
try:
    from typing import Any
except ImportError:
    pass

import os
import sys

from marple.arraymodel import APLArray
from marple.errors import APLError
from marple.interpreter import _DfnClosure, default_env, interpret
from marple.parser import Assignment, Program, parse
try:
    from marple.terminal import read_line
except ImportError:
    read_line = None  # type: ignore[assignment]
try:
    from marple.workspace import save_workspace, load_workspace, list_workspaces
except ImportError:
    save_workspace = load_workspace = list_workspaces = None  # type: ignore[assignment]

try:
    WORKSPACES_ROOT = os.environ.get("MARPLE_WORKSPACES", "workspaces")
except AttributeError:
    WORKSPACES_ROOT = "workspaces"


def _rjust(s, width):
    if len(s) >= width:
        return s
    return " " * (width - len(s)) + s


def _is_char_array(arr: APLArray) -> bool:
    return len(arr.data) > 0 and all(isinstance(x, str) for x in arr.data)


from marple.formatting import format_num as _format_num


def format_result(result: APLArray, env: dict[str, Any] | None = None) -> str:
    pp = 10
    if env is not None:
        pp_val = env.get("⎕PP")
        if pp_val is not None:
            pp = int(pp_val.data[0])
    if result.is_scalar():
        return _format_num(result.data[0], pp)
    if _is_char_array(result):
        if len(result.shape) == 1:
            return "".join(str(x) for x in result.data)
        if len(result.shape) == 2:
            rows, cols = result.shape
            lines = []
            for r in range(rows):
                row_data = result.data[r * cols : (r + 1) * cols]
                lines.append("".join(str(x) for x in row_data))
            return "\n".join(lines)
    if len(result.shape) == 1:
        return " ".join(_format_num(x, pp) for x in result.data)
    if len(result.shape) == 2:
        rows, cols = result.shape
        strs = [_format_num(result.data[r * cols + c], pp) for r in range(rows) for c in range(cols)]
        col_widths = []
        for c in range(cols):
            w = max(len(strs[r * cols + c]) for r in range(rows))
            col_widths.append(w)
        lines = []
        for r in range(rows):
            parts = []
            for c in range(cols):
                parts.append(_rjust(strs[r * cols + c], col_widths[c]))
            lines.append(" ".join(parts))
        return "\n".join(lines)
    return repr(result)


def _is_silent(line: str) -> bool:
    """Check if a line is a comment, bare assignment, or directive (should not print)."""
    stripped = line.strip()
    if stripped.startswith("#"):
        return True
    if stripped.startswith("⍝"):
        return True
    try:
        tree = parse(line)
    except Exception:
        return False
    if isinstance(tree, Assignment):
        return True
    if isinstance(tree, Program):
        return len(tree.statements) > 0 and isinstance(tree.statements[-1], Assignment)
    return False


def _user_names(env: dict[str, Any]) -> list[str]:
    return sorted(
        name for name in env
        if not name.startswith("⎕") and not name.startswith("__")
        and name not in ("⍵", "⍺", "∇")
    )


def main() -> None:
    # Check for script mode: marple script.marple
    if len(sys.argv) > 1:
        from marple.script import run_script
        path = sys.argv[1]
        for line in run_script(path):
            print(line)
        return

    env: dict[str, Any] = default_env()
    from marple import __version__ as ver
    print(f"MARPLE v{ver} - Mini APL in Python")
    print("CLEAR WS\n")
    use_terminal = read_line is not None and sys.stdin.isatty()
    while True:
        if use_terminal:
            line = read_line()
        else:
            try:
                line = input("      ")
            except (EOFError, KeyboardInterrupt):
                print()
                break
        if line is None:
            break
        line = line.strip()
        if not line:
            continue
        if line == ")off":
            break
        if line == ")clear":
            wsid = env.get("__wsid__", "CLEAR WS")
            env.clear()
            env["__wsid__"] = "CLEAR WS"
            print("CLEAR WS")
            continue
        if line == ")wsid" or line == ")WSID":
            print(env.get("__wsid__", "CLEAR WS"))
            continue
        if line.startswith(")wsid ") or line.startswith(")WSID "):
            new_wsid = line.split(None, 1)[1].strip()
            env["__wsid__"] = new_wsid
            print(new_wsid)
            continue
        if line.startswith(")fns"):
            parts = line.split(None, 1)
            if len(parts) > 1:
                # )fns $::str or )fns utils
                ns_name = parts[1].strip()
                from marple.interpreter import _get_sys_workspace
                if ns_name.startswith("$"):
                    ns_parts = ns_name.split("::")[1:] if "::" in ns_name else []
                    sys_ws = _get_sys_workspace()
                    ns = sys_ws.resolve(ns_parts) if ns_parts else sys_ws
                    if ns is not None and hasattr(ns, "list_names"):
                        print("  ".join(ns.list_names()))
                    else:
                        print(f"Namespace not found: {ns_name}")
                else:
                    print(f"Namespace not found: {ns_name}")
            else:
                fns = [n for n in _user_names(env) if isinstance(env[n], _DfnClosure)]
                print("  ".join(fns) if fns else "")
            continue
        if line == ")vars":
            vars_ = [n for n in _user_names(env) if isinstance(env[n], APLArray)]
            print("  ".join(vars_) if vars_ else "")
            continue
        if line == ")lib" or line == ")LIB":
            workspaces = list_workspaces(WORKSPACES_ROOT)
            if workspaces:
                print("  ".join(workspaces))
            else:
                print("(none)")
            continue
        if line.startswith(")save") or line.startswith(")SAVE"):
            parts = line.split(None, 1)
            if len(parts) > 1:
                env["__wsid__"] = parts[1].strip()
            wsid = env.get("__wsid__", "CLEAR WS")
            if wsid == "CLEAR WS":
                print("ERROR: No workspace ID set. Use )WSID name first.")
                continue
            ws_dir = os.path.join(WORKSPACES_ROOT, wsid)
            try:
                save_workspace(env, ws_dir)
                print(f"{wsid} SAVED")
            except Exception as e:
                print(f"ERROR: {e}")
            continue
        if line.startswith(")load") or line.startswith(")LOAD"):
            parts = line.split(None, 1)
            if len(parts) < 2:
                print("ERROR: )LOAD requires a workspace name")
                continue
            name = parts[1].strip()
            ws_dir = os.path.join(WORKSPACES_ROOT, name)
            if not os.path.isdir(ws_dir):
                print(f"ERROR: Workspace not found: {name}")
                continue
            env.clear()
            try:
                load_workspace(env, ws_dir)
                print(env.get("__wsid__", name))
            except Exception as e:
                print(f"ERROR: {e}")
            continue
        try:
            result = interpret(line, env)
            if not _is_silent(line):
                print(format_result(result, env))
        except APLError as e:
            print(e)
        except Exception as e:
            print(f"ERROR: {e}")


if __name__ == "__main__":
    main()
