import { useState, useEffect, useCallback, useRef } from 'react';
import './App.css';
import type { Observation, Door, ApiReport, SceneResponse } from './types';
import Map2D from './components/Map2D';
import HUD from './components/HUD';
import ControlPanel from './components/ControlPanel';
import APIReport from './components/APIReport';

const DOOR_CLOSED = 3;
const OBSTACLE    = 5;

interface EventEntry {
  step: number;
  text: string;
  reward: number;
  isAlarm?: boolean;
}

function App() {
  const [observation,    setObservation]    = useState<Observation | null>(null);
  const [sceneData,      setSceneData]      = useState<SceneResponse | null>(null);
  const [isAutoWait,     setIsAutoWait]     = useState(false);
  const [isPolling,      setIsPolling]      = useState(true);
  const [status,         setStatus]         = useState('Idle — waiting for connection');
  const [isError,        setIsError]        = useState(false);
  const [apiReport,      setApiReport]      = useState<ApiReport | null>(null);
  const [agentMoveCount, setAgentMoveCount] = useState(0);
  const [agentMoveFlash, setAgentMoveFlash] = useState(0);
  const [eventLog,       setEventLog]       = useState<EventEntry[]>([]);

  const prevAgentPos  = useRef({ x: -1, y: -1 });
  const autoWaitTimer = useRef<number | null>(null);
  const pollTimer     = useRef<number | null>(null);
  const logEndRef     = useRef<HTMLDivElement | null>(null);

  /* scroll event log to bottom */
  useEffect(() => {
    logEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [eventLog]);

  const setStatusMsg = (msg: string, error = false) => {
    setStatus(msg);
    setIsError(error);
  };

  const pushLog = (text: string, step: number, reward: number, isAlarm = false) => {
    setEventLog(prev => [...prev.slice(-49), { step, text, reward, isAlarm }]);
  };

  const applyObservation = useCallback((obs: Observation) => {
    const newX = obs.map_state.agent_x;
    const newY = obs.map_state.agent_y;
    if (prevAgentPos.current.x !== -1 &&
       (newX !== prevAgentPos.current.x || newY !== prevAgentPos.current.y)) {
      setAgentMoveFlash(18);
      setAgentMoveCount(c => c + 1);
    }
    prevAgentPos.current = { x: newX, y: newY };
    setObservation(obs);
  }, []);

  const updateReport = (kind: string, request: unknown, response: any) => {
    const mapState = response?.observation?.map_state || response?.map_state || response?.graph;
    const template = mapState?.template_name || response?.labels?.episode?.template || 'unknown';
    const step     = mapState?.step_count ?? response?.observation?.elapsed_steps ?? response?.labels?.episode?.step ?? '-';
    const reward   = Number(response?.reward ?? 0).toFixed(3);
    const done     = Boolean(response?.done);
    setApiReport({
      call_type: kind,
      request,
      response,
      meta: `${kind.toUpperCase()} | template=${template} | step=${step} | reward=${reward} | done=${done}`,
    });
  };

  const apiCall = async (path: string, payload: unknown) => {
    const res = await fetch(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload || {}),
    });
    if (!res.ok) {
      const text = await res.text();
      throw new Error(`${res.status} ${res.statusText}: ${text}`);
    }
    return res.json();
  };

  const resetLive = async (difficulty = 'medium') => {
    try {
      setStatusMsg('Initiating Reset...');
      const payload = { difficulty };
      const data = await apiCall('/reset', payload);
      const obs: Observation = data.observation;
      if (data.observation?.map_state) {
        obs.metadata = {
          fire_sources:    data.observation.fire_sources_count ?? 0,
          fire_spread_rate:data.observation.fire_spread_rate   ?? 0,
          humidity:        data.observation.humidity           ?? 0,
          difficulty,
        };
      }
      applyObservation(obs);
      updateReport('reset', payload, data);
      setStatusMsg(`Ready. Reward: ${Number(data.reward || 0).toFixed(2)}`);
      pushLog('Episode reset. Assess surroundings.', obs.map_state.step_count, data.reward ?? 0);
    } catch (err: any) {
      setStatusMsg(`Reset Failed: ${err.message}`, true);
    }
  };

  const resetUntilDoors = async () => {
    try {
      setStatusMsg('Searching for layout with doors...');
      for (let i = 1; i <= 8; i++) {
        const payload = { difficulty: 'medium' };
        const data    = await apiCall('/reset', payload);
        const doorCount = Object.keys(data?.observation?.map_state?.door_registry || {}).length;
        if (doorCount > 0) {
          const obs: Observation = data.observation;
          obs.metadata = {
            fire_sources:    data.observation.fire_sources_count ?? 0,
            fire_spread_rate:data.observation.fire_spread_rate   ?? 0,
            humidity:        data.observation.humidity           ?? 0,
            difficulty:      'medium',
          };
          applyObservation(obs);
          updateReport('reset', payload, data);
          setStatusMsg(`System Ready — ${doorCount} door(s) detected.`);
          pushLog(`Layout found (${doorCount} doors). Doors detected.`, obs.map_state.step_count, 0);
          return;
        }
      }
      setStatusMsg('Optimal layout not found after 8 attempts.', true);
    } catch (err: any) {
      setStatusMsg(`Search Failed: ${err.message}`, true);
    }
  };

  const runAction = async (actionObj: unknown, label: string) => {
    try {
      setStatusMsg(`Action: ${label}`);
      const payload = { action: actionObj };
      const data    = await apiCall('/step', payload);
      const obs: Observation = data.observation;
      obs.metadata = observation?.metadata;
      applyObservation(obs);
      updateReport('step', payload, data);
      const rwd = Number(data.reward || 0);
      setStatusMsg(`Executed. Reward: ${rwd.toFixed(2)}`);
      pushLog(
        obs.last_action_feedback || label,
        obs.map_state.step_count,
        rwd,
        rwd < -0.5,
      );
      if (data.done) setIsAutoWait(false);
    } catch (err: any) {
      setStatusMsg(`Error: ${err.message}`, true);
    }
  };

  const fetchAndApplyScene = useCallback(async () => {
    try {
      const res = await fetch('/scene');
      if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
      const scene: SceneResponse = await res.json();
      setSceneData(scene);
      updateReport('scene', {}, scene);

      const { labels, graph } = scene;
      const cell_grid:  number[] = [];
      const fire_grid:  number[] = [];
      const smoke_grid: number[] = [];

      for (let y = 0; y < graph.height; y++) {
        for (let x = 0; x < graph.width; x++) {
          const [type, fire, smoke] = graph.grid[y][x];
          cell_grid.push(type);
          fire_grid.push(fire);
          smoke_grid.push(smoke);
        }
      }

      const visible_cells: [number, number][] = [];
      for (let y = 0; y < graph.height; y++) {
        for (let x = 0; x < graph.width; x++) {
          if (graph.grid[y][x][4] === 1.0) visible_cells.push([x, y]);
        }
      }

      const pseudoObs: Observation = {
        map_state: {
          cell_grid, fire_grid, smoke_grid,
          agent_x:      labels.agent.x,
          agent_y:      labels.agent.y,
          visible_cells,
          door_registry:  labels.map.door_registry,
          exit_positions: labels.map.exit_positions,
          step_count:     labels.episode.step,
          max_steps:      labels.episode.max_steps,
          grid_w:         graph.width,
          grid_h:         graph.height,
          template_name:  labels.episode.template,
        },
        agent_health:         labels.agent.health,
        location_label:       labels.agent.location,
        smoke_level:          labels.agent.smoke_level,
        wind_dir:             labels.episode.wind_dir,
        fire_visible:         labels.agent.fire_visible,
        fire_direction:       labels.agent.fire_direction,
        last_action_feedback: labels.agent.last_action_feedback,
        narrative: '',
        metadata: {
          fire_sources:    labels.episode.fire_sources,
          fire_spread_rate:labels.episode.fire_spread_rate,
          humidity:        labels.episode.humidity,
          difficulty:      labels.episode.difficulty,
        },
      };
      applyObservation(pseudoObs);
    } catch (err: any) {
      setStatusMsg(`Sync Error: ${err.message}`, true);
    }
  }, [applyObservation, observation?.metadata]);

  useEffect(() => {
    if (isAutoWait) {
      autoWaitTimer.current = window.setInterval(() => runAction({ action: 'wait' }, 'AUTO WAIT'), 900);
    } else {
      if (autoWaitTimer.current) clearInterval(autoWaitTimer.current);
    }
    return () => { if (autoWaitTimer.current) clearInterval(autoWaitTimer.current); };
  }, [isAutoWait]);

  useEffect(() => {
    if (isPolling) {
      fetchAndApplyScene();
      pollTimer.current = window.setInterval(fetchAndApplyScene, 500);
    } else {
      if (pollTimer.current) clearInterval(pollTimer.current);
    }
    return () => { if (pollTimer.current) clearInterval(pollTimer.current); };
  }, [isPolling, fetchAndApplyScene]);

  useEffect(() => {
    if (agentMoveFlash > 0) {
      const timer = setTimeout(() => setAgentMoveFlash(f => f - 1), 50);
      return () => clearTimeout(timer);
    }
  }, [agentMoveFlash]);

  const setup = async () => {
    setIsPolling(false);
    setAgentMoveCount(0);
    setAgentMoveFlash(0);
    setEventLog([]);
    prevAgentPos.current = { x: -1, y: -1 };
    await resetLive();
    setIsPolling(true);
  };

  /* Derived state */
  const doors: Door[] = Object.entries(observation?.map_state.door_registry || {})
    .map(([id, [x, y]]) => {
      const ct = observation?.map_state.cell_grid[y * (observation?.map_state.grid_w ?? 16) + x];
      let state: 'open' | 'closed' | 'failed' = 'open';
      if (ct === DOOR_CLOSED) state = 'closed';
      if (ct === OBSTACLE)   state = 'failed';
      return { id, x, y, state };
    })
    .sort((a, b) => a.id.localeCompare(b.id, undefined, { numeric: true }));

  const fireCells   = observation?.map_state.fire_grid.filter(v => v > 0.05).length ?? 0;
  const exploredPct = observation
    ? Math.round((new Set(observation.map_state.visible_cells.map(([vx, vy]) => `${vx},${vy}`)).size
        / observation.map_state.cell_grid.length) * 100)
    : 0;

  const hp        = Math.round(observation?.agent_health ?? 0);
  const hpColor   = hp >= 60 ? 'var(--green)' : hp >= 30 ? 'var(--amber)' : 'var(--red)';
  const isOnline  = isPolling && !isError;
  const epId      = sceneData?.labels.episode.id?.slice(0, 8) ?? '—';

  return (
    <div className="shell">
      {/* ── Topbar ── */}
      <header className="topbar">
        <div className="brand">
          <div className="brand-icon">🔥</div>
          <span className="brand-name">Pyre</span>
          <span className="brand-sep">/</span>
          <span className="brand-sub">Crisis Navigation</span>
        </div>

        <div className="topbar-right">
          <div className="topbar-ep">
            Episode <span className="ep-id">{epId}</span>
          </div>
          <span className={`live-chip ${isError ? 'error' : isOnline ? 'online' : 'offline'}`}>
            {isError ? 'Error' : isOnline ? 'Live' : 'Idle'}
          </span>
        </div>
      </header>

      {/* ── Body ── */}
      <div className="content">

        {/* ── Left: Canvas Zone ── */}
        <div className="canvas-zone">
          <div className="canvas-frame">
            <Map2D observation={observation} agentMoveFlash={agentMoveFlash} />
            <HUD observation={observation} agentMoveCount={agentMoveCount} />
          </div>

          {/* Legend */}
          <div className="legend">
            {[
              { color:'#5e5850', label:'Wall'      },
              { color:'#3a3530', label:'Obstacle'  },
              { color:'#e6f4ec', label:'Exit'      },
              { color:'#7c5c3c', label:'Door'      },
              { color:'#f97316', label:'Fire'      },
              { color:'rgba(72,82,96,0.7)', label:'Smoke' },
              { color:'#3b82f6', label:'Agent'     },
              { color:'rgba(2,132,199,0.6)', label:'Trail'},
            ].map(({ color, label }) => (
              <div key={label} className="legend-item">
                <div className="leg-swatch" style={{ background: color, border:'1px solid rgba(0,0,0,0.1)' }} />
                {label}
              </div>
            ))}
          </div>

          {/* Field Report */}
          <div className="dialog">
            <span className="dialog-who">Field Report</span>
            {observation?.last_action_feedback || 'Establishing link to field systems...'}
          </div>
        </div>

        {/* ── Right: Side Panel ── */}
        <aside className="side">

          {/* Controls */}
          <ControlPanel
            onAction={runAction}
            onReset={resetLive}
            onResetDoors={resetUntilDoors}
            onSetup={setup}
            doors={doors}
            isAutoWait={isAutoWait}
            toggleAutoWait={() => setIsAutoWait(!isAutoWait)}
            isPolling={isPolling}
            togglePolling={() => setIsPolling(!isPolling)}
            status={status}
            isError={isError}
          />

          {/* Agent Biometrics */}
          <div className="side-sec">
            <div className="sec-hd">Agent Biometrics</div>
            <div className="sg">
              <div className="sc">
                <div className="sc-l">Health</div>
                <div className="sc-v" style={{ color: hpColor }}>{hp}%</div>
              </div>
              <div className="sc">
                <div className="sc-l">Status</div>
                <div className="sc-v" style={{ color: hpColor }}>
                  {sceneData?.labels.agent.health_status ?? 'NOMINAL'}
                </div>
              </div>
              <div className="sc">
                <div className="sc-l">Position</div>
                <div className="sc-v blue">
                  ({observation?.map_state.agent_x ?? '—'},{observation?.map_state.agent_y ?? '—'})
                </div>
              </div>
              <div className="sc">
                <div className="sc-l">Sector</div>
                <div className="sc-v">{observation?.location_label ?? 'Unknown'}</div>
              </div>
            </div>
            <div className="bar-w">
              <div className="bar-lbl">
                <span>System Integrity</span>
                <span>{hp}%</span>
              </div>
              <div className="bar-bg">
                <div className="bar-fill" style={{ width: `${hp}%`, background: hpColor }} />
              </div>
            </div>
          </div>

          {/* Environment */}
          <div className="side-sec">
            <div className="sec-hd">Environment</div>
            <div className="sg">
              <div className="sc">
                <div className="sc-l">Hazard Cells</div>
                <div className="sc-v fire">{fireCells}</div>
              </div>
              <div className="sc">
                <div className="sc-l">Explored</div>
                <div className="sc-v blue">{exploredPct}%</div>
              </div>
              <div className="sc">
                <div className="sc-l">Wind</div>
                <div className="sc-v">{observation?.wind_dir ?? 'CALM'}</div>
              </div>
              <div className="sc">
                <div className="sc-l">Humidity</div>
                <div className="sc-v amber">
                  {Math.round((observation?.metadata?.humidity ?? 0) * 100)}%
                </div>
              </div>
            </div>
          </div>

          {/* Event Log */}
          <div className="side-sec" style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
            <div className="sec-hd">
              Event Log
              <span style={{ fontFamily: 'var(--mono)', fontSize: '9px', color: 'var(--t3)' }}>
                {eventLog.length} events
              </span>
            </div>
            <div className="elog">
              {eventLog.length === 0 && (
                <div style={{ color: 'var(--t3)', fontFamily: 'var(--mono)', fontSize: '10px', padding: '4px' }}>
                  No events yet…
                </div>
              )}
              {eventLog.map((e, i) => (
                <div key={i} className={`erow ${e.isAlarm ? 'alarm' : ''}`}>
                  <span className="estep">T{e.step}</span>
                  <span className="etext">{e.text}</span>
                  <span className={`erwd ${e.reward >= 0 ? 'p' : 'n'}`}>
                    {e.reward >= 0 ? '+' : ''}{e.reward.toFixed(2)}
                  </span>
                </div>
              ))}
              <div ref={logEndRef} />
            </div>
          </div>

          {/* API Report */}
          <div className="side-sec">
            <div className="sec-hd">Network Activity</div>
            <APIReport report={apiReport} onCopyReset={() => {}} onCopyStep={() => {}} onCopyScene={() => {}} />
          </div>

        </aside>
      </div>
    </div>
  );
}

export default App;
