#!/usr/bin/env python3
"""Simple HTTP server for dashboard, kanban, and action board.

Supports a minimal POST API for persisting Action Board drag/drop overrides and
(optionally) syncing those moves into Kanban state.

Zero external dependencies.
"""

import http.server
import json
import socketserver
import subprocess
import threading
from pathlib import Path

PORT = 8080
DIR = Path(__file__).parent  # /home/isthekid/.openclaw/workspace

ACTION_STATE = DIR / 'action' / 'action-state.json'
KANBAN_STATE = DIR / 'kanban' / 'kanban-state.json'
BUILD_KANBAN = DIR / 'kanban' / 'build-kanban-data.py'

_LOCK = threading.Lock()


def _read_json(path: Path, default):
    try:
        return json.loads(path.read_text())
    except Exception:
        return default


def _write_json(path: Path, obj) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(obj, indent=2))


def _map_action_col_to_kanban_col(action_col: str) -> str:
    # Action board columns: overdue, urgent, do_now, waiting, quick_wins, someday, done_recent
    # Kanban columns: todo, doing, waiting, done
    action_col = (action_col or '').lower()
    if action_col == 'waiting':
        return 'waiting'
    if action_col in ('done_recent', 'done'):
        return 'done'
    if action_col == 'do_now':
        return 'doing'
    # everything else is actionable but not "in progress" yet
    return 'todo'


class Handler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(DIR), **kwargs)

    def end_headers(self):
        # Disable caching so data.json always reloads
        self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate')
        self.send_header('Pragma', 'no-cache')
        self.send_header('Expires', '0')
        super().end_headers()

    def _send_json(self, status: int, payload):
        data = json.dumps(payload).encode('utf-8')
        self.send_response(status)
        self.send_header('Content-Type', 'application/json; charset=utf-8')
        self.send_header('Content-Length', str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def do_POST(self):
        # Minimal API for Action Board
        if self.path.rstrip('/') == '/action/api/move':
            try:
                length = int(self.headers.get('Content-Length', '0') or '0')
                raw = self.rfile.read(length).decode('utf-8')
                body = json.loads(raw or '{}')

                project_ids = body.get('projectIds') or []
                target = body.get('targetColumnId') or ''
                reason = body.get('reason') or 'user'

                if not isinstance(project_ids, list) or not all(isinstance(x, str) for x in project_ids):
                    return self._send_json(400, {'ok': False, 'error': 'projectIds must be string[]'})
                if not isinstance(target, str) or not target:
                    return self._send_json(400, {'ok': False, 'error': 'targetColumnId required'})

                with _LOCK:
                    # Update action state
                    action_state = _read_json(
                        ACTION_STATE,
                        {
                            'meta': {'title': 'Action Board', 'timezone': 'America/New_York'},
                            'preferences': {
                                'urgentDaysThreshold': 3,
                                'doNowDaysThreshold': 7,
                                'doneRecentDays': 7,
                                'quickWinMaxActions': 3,
                            },
                            'manualOverrides': {},
                            'dismissedProjects': [],
                        },
                    )
                    action_state.setdefault('manualOverrides', {})

                    for pid in project_ids:
                        action_state['manualOverrides'][pid] = {
                            'columnId': target,
                            'timestamp': self.date_time_string(),
                            'reason': reason,
                        }
                    _write_json(ACTION_STATE, action_state)

                    # Sync to Kanban overlay
                    kanban_state = _read_json(
                        KANBAN_STATE,
                        {
                            'meta': {'title': 'Adner Kanban (overlay)', 'timezone': 'America/New_York'},
                            'columns': [
                                {'id': 'todo', 'name': 'To Do'},
                                {'id': 'doing', 'name': 'Doing'},
                                {'id': 'waiting', 'name': 'Waiting'},
                                {'id': 'done', 'name': 'Done'},
                            ],
                            'cardState': {},
                            'manualCards': [],
                        },
                    )
                    kanban_state.setdefault('cardState', {})
                    kcol = _map_action_col_to_kanban_col(target)
                    for pid in project_ids:
                        kanban_state['cardState'][pid] = {'column': kcol}

                    _write_json(KANBAN_STATE, kanban_state)

                    # Rebuild kanban/data.json (so kanban page reflects immediately)
                    if BUILD_KANBAN.exists():
                        subprocess.run([str(BUILD_KANBAN)], check=False)

                return self._send_json(200, {'ok': True})
            except Exception as e:
                return self._send_json(500, {'ok': False, 'error': str(e)})

        return self._send_json(404, {'ok': False, 'error': 'not found'})


def main():
    with socketserver.ThreadingTCPServer(("", PORT), Handler) as httpd:
        print(f"✓ Serving workspace at http://localhost:{PORT}/")
        print(f"  Dashboard: http://localhost:{PORT}/dashboard/")
        print(f"  Kanban:    http://localhost:{PORT}/kanban/")
        print(f"  Action:    http://localhost:{PORT}/action/")
        print("Press Ctrl+C to stop.")
        httpd.serve_forever()


if __name__ == '__main__':
    main()
