"""Board state management with persistence."""

import json
from pathlib import Path
from typing import Callable
from datetime import datetime

from .models import Board, Task, ColumnId, AIStatus


class BoardState:
    """Reactive board state with persistence."""

    def __init__(self, storage_path: Path | None = None):
        """Initialize board state.

        Args:
            storage_path: Path to save board state. Defaults to .emdash/kanban/board.json
        """
        if storage_path is None:
            storage_path = Path.cwd() / ".emdash" / "kanban" / "board.json"
        self.storage_path = storage_path
        self._board = Board()
        self._subscribers: list[Callable[[str, Task | None], None]] = []
        self._load()

    @property
    def board(self) -> Board:
        """Get the current board."""
        return self._board

    def subscribe(self, callback: Callable[[str, Task | None], None]) -> None:
        """Subscribe to state changes.

        Args:
            callback: Function called with (event_type, task) on changes.
                     event_type: 'add', 'update', 'delete', 'move'
        """
        self._subscribers.append(callback)

    def unsubscribe(self, callback: Callable[[str, Task | None], None]) -> None:
        """Unsubscribe from state changes."""
        if callback in self._subscribers:
            self._subscribers.remove(callback)

    def _notify(self, event_type: str, task: Task | None = None) -> None:
        """Notify all subscribers of a state change."""
        for callback in self._subscribers:
            try:
                callback(event_type, task)
            except Exception:
                pass  # Don't let subscriber errors break state management

    def add_task(self, title: str, description: str = "") -> Task:
        """Add a new task to the backlog."""
        task = self._board.add_task(title, description)
        self._save()
        self._notify("add", task)
        return task

    def move_task(self, task_id: str, column: ColumnId) -> Task | None:
        """Move a task to a different column."""
        task = self._board.move_task(task_id, column)
        if task:
            self._save()
            self._notify("move", task)
        return task

    def update_task(self, task_id: str, **kwargs) -> Task | None:
        """Update a task's fields."""
        task = self._board.update_task(task_id, **kwargs)
        if task:
            self._save()
            self._notify("update", task)
        return task

    def delete_task(self, task_id: str) -> bool:
        """Delete a task from the board."""
        task = self._board.tasks.get(task_id)
        if self._board.delete_task(task_id):
            self._save()
            self._notify("delete", task)
            return True
        return False

    def get_task(self, task_id: str) -> Task | None:
        """Get a task by ID."""
        return self._board.tasks.get(task_id)

    def get_tasks_by_column(self, column: ColumnId) -> list[Task]:
        """Get all tasks in a column."""
        return self._board.get_tasks_by_column(column)

    def get_all_tasks(self) -> list[Task]:
        """Get all tasks."""
        return list(self._board.tasks.values())

    def start_task(self, task_id: str) -> Task | None:
        """Start a task - move it to planning."""
        task = self.get_task(task_id)
        if task and task.column == ColumnId.BACKLOG:
            task.column = ColumnId.PLANNING
            task.ai_status = AIStatus.QUEUED
            task.updated_at = datetime.now()
            self._save()
            self._notify("move", task)
            return task
        return None

    def _save(self) -> None:
        """Save board to disk."""
        try:
            self.storage_path.parent.mkdir(parents=True, exist_ok=True)
            data = self._board.model_dump(mode="json")
            # Convert datetime objects to ISO strings
            for task_data in data.get("tasks", {}).values():
                if isinstance(task_data.get("created_at"), datetime):
                    task_data["created_at"] = task_data["created_at"].isoformat()
                if isinstance(task_data.get("updated_at"), datetime):
                    task_data["updated_at"] = task_data["updated_at"].isoformat()
            self.storage_path.write_text(json.dumps(data, indent=2, default=str))
        except Exception as e:
            # Log but don't crash on save errors
            pass

    def _load(self) -> None:
        """Load board from disk."""
        try:
            if self.storage_path.exists():
                data = json.loads(self.storage_path.read_text())
                # Convert ISO strings back to datetime objects
                for task_data in data.get("tasks", {}).values():
                    if isinstance(task_data.get("created_at"), str):
                        task_data["created_at"] = datetime.fromisoformat(
                            task_data["created_at"]
                        )
                    if isinstance(task_data.get("updated_at"), str):
                        task_data["updated_at"] = datetime.fromisoformat(
                            task_data["updated_at"]
                        )
                self._board = Board.model_validate(data)
        except Exception:
            # Start with empty board on load errors
            self._board = Board()
