# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========

import asyncio
import io

from mirage.bridge.sync import run_async_from_sync
from mirage.ops import Ops


class MirageFile:

    def __init__(
        self,
        ops: Ops,
        path: str,
        mode: str = "r",
        loop: asyncio.AbstractEventLoop | None = None,
        **kwargs,
    ) -> None:
        self._ops = ops
        self._path = path
        self._mode = mode
        self._loop = loop
        self._encoding = kwargs.get("encoding", "utf-8")
        self._binary = "b" in mode
        self._closed = False
        self._buf: io.BytesIO | io.StringIO | None = None

    def _run(self, coro):
        return run_async_from_sync(coro, self._loop)

    def _load(self) -> None:
        if self._buf is not None:
            return
        if "w" in self._mode:
            if self._binary:
                self._buf = io.BytesIO()
            else:
                self._buf = io.StringIO()
            return
        if "a" in self._mode:
            try:
                data = self._run(self._ops.read(self._path))
            except FileNotFoundError:
                data = b""
            if self._binary:
                self._buf = io.BytesIO(data)
            else:
                self._buf = io.StringIO(data.decode(self._encoding))
            self._buf.seek(0, 2)
            return
        data = self._run(self._ops.read(self._path))
        if self._binary:
            self._buf = io.BytesIO(data)
        else:
            self._buf = io.StringIO(data.decode(self._encoding))

    @property
    def closed(self) -> bool:
        return self._closed

    @property
    def name(self) -> str:
        return self._path

    @property
    def mode(self) -> str:
        return self._mode

    def readable(self) -> bool:
        return "r" in self._mode

    def writable(self) -> bool:
        return "w" in self._mode or "a" in self._mode

    def read(self, size: int = -1) -> bytes | str:
        self._load()
        return self._buf.read(size)

    def readline(self) -> bytes | str:
        self._load()
        return self._buf.readline()

    def readlines(self) -> list:
        self._load()
        return self._buf.readlines()

    def write(self, data) -> int:
        self._load()
        return self._buf.write(data)

    def writelines(self, lines) -> None:
        self._load()
        self._buf.writelines(lines)

    def seek(self, offset: int, whence: int = 0) -> int:
        self._load()
        return self._buf.seek(offset, whence)

    def tell(self) -> int:
        self._load()
        return self._buf.tell()

    def flush(self) -> None:
        pass

    def close(self) -> None:
        if self._closed:
            return
        self._closed = True
        if "w" in self._mode or "a" in self._mode:
            if self._buf is not None:
                val = self._buf.getvalue()
                if not self._binary:
                    val = val.encode(self._encoding)
                self._run(self._ops.write(self._path, val))

    def __del__(self) -> None:
        self.close()

    def __enter__(self):
        return self

    def __exit__(self, *exc):
        self.close()

    def __iter__(self):
        self._load()
        return iter(self._buf)

    def __next__(self):
        self._load()
        return next(self._buf)
