/* global React, Icon, IconBtn, db */
/* global ttFmtDuration, ttFmtClock, ttStartOfDay, ttStartOfWeek, ttFmtDayLabel, ttProjectById, ttTaskById, useTtNow */
/* global TtProjectDot */
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// TtCalendarView — sessions as blocks on a day/week grid. Each
// block belongs to a task (looked up for its label + color). Drag
// empty space to create, drag a block to move, drag its edges to
// resize. Overlaps are laid out side-by-side (visualized, never blocked).
// ============================================================

const TT_HOUR_PX = 48;
const TT_GUTTER_PX = 56;
const TT_SNAP_MIN = 15;
const TT_RESIZE_SNAP_MIN = 5;
const TT_MIN_DUR_MIN = 5;

function ttSnap(min, step) { return Math.round(min / step) * step; }

function TtCalendarView({ sessions, tasksById, projects, runningList = [], settings, onEditTask, onCreate }) {
  const [mode, setMode] = useState('week'); // 'week' | 'day'
  const [anchor, setAnchor] = useState(() => ttStartOfDay(Date.now()));
  const [drag, setDrag] = useState(null);
  const bodyRef = useRef(null);
  const dragRef = useRef(null);
  const eatClickRef = useRef(false);
  const now = useTtNow(true);
  dragRef.current = drag;

  const days = useMemo(() => {
    if (mode === 'day') return [anchor];
    const w = ttStartOfWeek(anchor, settings.weekStartsMonday);
    return Array.from({ length: 7 }, (_, i) => w + i * 86400000);
  }, [mode, anchor, settings.weekStartsMonday]);

  // Scroll to 7am on first render
  useEffect(() => {
    if (bodyRef.current) bodyRef.current.scrollTop = 7 * TT_HOUR_PX;
  }, []);

  const nav = (dir) => {
    setAnchor(a => a + dir * (mode === 'day' ? 1 : 7) * 86400000);
  };

  // ---------- segments per day ----------
  // Preview override while dragging (move/resize)
  const preview = drag && (drag.kind === 'move' || drag.kind === 'resize') && drag.moved
    ? { id: drag.entry.id, startedAt: drag.previewStart, stoppedAt: drag.previewStop }
    : null;

  const allBlocks = useMemo(() => {
    const list = sessions.map(e => {
      if (preview && e.id === preview.id) return { ...e, startedAt: preview.startedAt, stoppedAt: preview.stoppedAt };
      return e;
    });
    for (const run of runningList) {
      list.push({
        id: '__running_' + run.id, taskId: run.taskId,
        description: run.description, projectId: run.projectId,
        startedAt: run.startedAt, stoppedAt: now, __running: true,
      });
    }
    return list;
  }, [sessions, runningList, now, preview && preview.id, preview && preview.startedAt, preview && preview.stoppedAt]);

  const segmentsByDay = useMemo(() => {
    return days.map(dayStart => {
      const dayEnd = dayStart + 86400000;
      const segs = [];
      for (const e of allBlocks) {
        if (e.stoppedAt <= dayStart || e.startedAt >= dayEnd) continue;
        const segStart = Math.max(e.startedAt, dayStart);
        const segEnd = Math.min(e.stoppedAt, dayEnd);
        segs.push({
          entry: e,
          startMin: (segStart - dayStart) / 60000,
          endMin: Math.max((segEnd - dayStart) / 60000, (segStart - dayStart) / 60000 + 4),
          isStart: e.startedAt >= dayStart,
          isEnd: e.stoppedAt <= dayEnd,
        });
      }
      // Overlap clusters → side-by-side columns
      segs.sort((a, b) => a.startMin - b.startMin || b.endMin - a.endMin);
      const colsEnd = []; // end minute of last seg in each column (within cluster)
      let clusterEnd = -1, clusterStart = 0;
      const finishCluster = (from, to, nCols) => {
        for (let i = from; i < to; i++) segs[i].cols = nCols;
      };
      for (let i = 0; i < segs.length; i++) {
        const s = segs[i];
        if (s.startMin >= clusterEnd) {
          finishCluster(clusterStart, i, colsEnd.length);
          colsEnd.length = 0; clusterStart = i; clusterEnd = -1;
        }
        let col = colsEnd.findIndex(end => end <= s.startMin);
        if (col === -1) { col = colsEnd.length; colsEnd.push(0); }
        colsEnd[col] = s.endMin;
        s.col = col;
        clusterEnd = Math.max(clusterEnd, s.endMin);
      }
      finishCluster(clusterStart, segs.length, colsEnd.length);
      return segs;
    });
  }, [days, allBlocks]);

  const dayTotals = useMemo(() => days.map(dayStart => {
    const dayEnd = dayStart + 86400000;
    let ms = 0;
    for (const e of allBlocks) {
      // daily totals attribute the whole entry to its start day (PRD)
      if (e.startedAt >= dayStart && e.startedAt < dayEnd) ms += (e.stoppedAt - e.startedAt);
    }
    return ms;
  }), [days, allBlocks]);

  // ---------- pointer math ----------
  const posFromEvent = useCallback((e) => {
    const body = bodyRef.current;
    const rect = body.getBoundingClientRect();
    const x = e.clientX - rect.left - TT_GUTTER_PX;
    const y = e.clientY - rect.top + body.scrollTop;
    const colW = (rect.width - TT_GUTTER_PX) / days.length;
    const dayIdx = Math.max(0, Math.min(days.length - 1, Math.floor(x / colW)));
    const min = Math.max(0, Math.min(1440, (y / TT_HOUR_PX) * 60));
    return { dayIdx, min };
  }, [days.length]);

  // ---------- drag lifecycle ----------
  useEffect(() => {
    if (!drag) return;
    const onMove = (e) => {
      const d = dragRef.current;
      if (!d) return;
      const { dayIdx, min } = posFromEvent(e);
      if (d.kind === 'create') {
        setDrag({ ...d, curMin: ttSnap(min, TT_SNAP_MIN), moved: true });
      } else if (d.kind === 'move') {
        const newStartMin = ttSnap(min - d.grabOffsetMin, TT_SNAP_MIN);
        const dur = d.entry.stoppedAt - d.entry.startedAt;
        const clamped = Math.max(0, Math.min(1440 - dur / 60000, newStartMin));
        const previewStart = days[dayIdx] + clamped * 60000;
        setDrag({ ...d, previewStart, previewStop: previewStart + dur, moved: true });
      } else if (d.kind === 'resize') {
        const m = ttSnap(min, TT_RESIZE_SNAP_MIN);
        const dayStart = d.dayStart;
        if (d.edge === 'top') {
          const maxStart = d.entry.stoppedAt - TT_MIN_DUR_MIN * 60000;
          const previewStart = Math.min(dayStart + m * 60000, maxStart);
          setDrag({ ...d, previewStart, previewStop: d.entry.stoppedAt, moved: true });
        } else {
          const minStop = d.entry.startedAt + TT_MIN_DUR_MIN * 60000;
          const previewStop = Math.max(dayStart + m * 60000, minStop);
          setDrag({ ...d, previewStart: d.entry.startedAt, previewStop, moved: true });
        }
      }
    };
    const onUp = () => {
      const d = dragRef.current;
      setDrag(null);
      if (!d) return;
      if (d.kind === 'create') {
        const a = Math.min(d.startMin, d.curMin);
        const b = Math.max(d.startMin, d.curMin);
        const startedAt = d.dayStart + a * 60000;
        const stoppedAt = d.dayStart + Math.max(b, a + 30) * 60000; // click → 30 min default
        onCreate({ startedAt, stoppedAt });
      } else if ((d.kind === 'move' || d.kind === 'resize') && d.moved) {
        eatClickRef.current = true;
        setTimeout(() => { eatClickRef.current = false; }, 100);
        // Don't allow dragging an entry onto a running timer
        if (runningList.some(r => d.previewStart < Date.now() && d.previewStop > r.startedAt)) return;
        if (d.previewStop > d.previewStart) {
          db.timeEntries.put({ ...d.entry, startedAt: d.previewStart, stoppedAt: d.previewStop });
        }
      }
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
    return () => {
      window.removeEventListener('pointermove', onMove);
      window.removeEventListener('pointerup', onUp);
    };
  }, [!!drag, posFromEvent, days, runningList, onCreate]);

  const startCreate = (e, dayStart) => {
    if (e.button !== 0) return;
    const { min } = posFromEvent(e);
    const m = ttSnap(min, TT_SNAP_MIN);
    setDrag({ kind: 'create', dayStart, startMin: m, curMin: m, moved: false });
  };

  const startMove = (e, entry, dayStart) => {
    if (e.button !== 0 || entry.__running) return;
    e.stopPropagation();
    const { min } = posFromEvent(e);
    const entryStartMin = (entry.startedAt - dayStart) / 60000;
    setDrag({
      kind: 'move', entry, dayStart,
      grabOffsetMin: min - entryStartMin,
      previewStart: entry.startedAt, previewStop: entry.stoppedAt,
      moved: false,
    });
  };

  const startResize = (e, entry, dayStart, edge) => {
    if (e.button !== 0 || entry.__running) return;
    e.stopPropagation();
    setDrag({
      kind: 'resize', entry, dayStart, edge,
      previewStart: entry.startedAt, previewStop: entry.stoppedAt,
      moved: false,
    });
  };

  const rangeLabel = mode === 'day'
    ? new Date(anchor).toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' })
    : (() => {
        const s = new Date(days[0]), e = new Date(days[6]);
        return `${s.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} – ${e.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}`;
      })();

  const today = ttStartOfDay(Date.now());
  const nowMin = (Date.now() - today) / 60000;

  return (
    <div className="trk-cal" data-screen-label="Time tracker calendar">
      <div className="trk-cal-toolbar">
        <div className="trk-cal-nav">
          <IconBtn name="chevron_left" title="Previous" onClick={() => nav(-1)}></IconBtn>
          <button className="lh-btn ghost" onClick={() => setAnchor(ttStartOfDay(Date.now()))}>Today</button>
          <IconBtn name="chevron_right" title="Next" onClick={() => nav(1)}></IconBtn>
        </div>
        <div className="trk-cal-range">{rangeLabel}</div>
        <div className="trk-cal-modes">
          <button className={`trk-view-tab ${mode === 'day' ? 'is-active' : ''}`} onClick={() => setMode('day')}>Day</button>
          <button className={`trk-view-tab ${mode === 'week' ? 'is-active' : ''}`} onClick={() => setMode('week')}>Week</button>
        </div>
      </div>

      <div className="trk-cal-headrow" style={{ paddingLeft: TT_GUTTER_PX }}>
        {days.map((d, i) => (
          <div key={d} className={`trk-cal-dayhead ${d === today ? 'is-today' : ''}`}>
            <span className="trk-cal-dayname">{ttFmtDayLabel(d)}</span>
            <span className="trk-cal-daytotal">{dayTotals[i] > 0 ? ttFmtDuration(dayTotals[i], { seconds: false }) : ''}</span>
          </div>
        ))}
      </div>

      <div className="trk-cal-body" ref={bodyRef}>
        <div className="trk-cal-grid" style={{ height: 24 * TT_HOUR_PX, gridTemplateColumns: `${TT_GUTTER_PX}px repeat(${days.length}, 1fr)` }}>
          <div className="trk-cal-gutter">
            {Array.from({ length: 24 }, (_, h) => (
              <div key={h} className="trk-cal-hour" style={{ top: h * TT_HOUR_PX }}>
                {h === 0 ? '' : `${((h + 11) % 12) + 1}${h < 12 ? ' AM' : ' PM'}`}
              </div>
            ))}
          </div>
          {days.map((dayStart, di) => (
            <div
              key={dayStart}
              className={`trk-cal-col ${dayStart === today ? 'is-today' : ''}`}
              onPointerDown={(e) => { if (e.target === e.currentTarget) startCreate(e, dayStart); }}
            >
              {Array.from({ length: 23 }, (_, h) => (
                <div key={h} className="trk-cal-line" style={{ top: (h + 1) * TT_HOUR_PX }}></div>
              ))}

              {dayStart === today && nowMin >= 0 && nowMin <= 1440 && (
                <div className="trk-cal-nowline" style={{ top: (nowMin / 60) * TT_HOUR_PX }}></div>
              )}

              {drag && drag.kind === 'create' && drag.dayStart === dayStart && (
                <div
                  className="trk-cal-ghost"
                  style={{
                    top: (Math.min(drag.startMin, drag.curMin) / 60) * TT_HOUR_PX,
                    height: Math.max(((Math.abs(drag.curMin - drag.startMin)) / 60) * TT_HOUR_PX, 12),
                  }}
                >
                  <span>New entry</span>
                </div>
              )}

              {segmentsByDay[di].map((seg, si) => {
                const e = seg.entry;
                const task = e.__running ? null : tasksById.get(e.taskId);
                const desc = e.__running ? (e.description || '') : (task ? task.description : '');
                const projId = e.__running ? e.projectId : (task ? task.projectId : null);
                const proj = ttProjectById(projects, projId);
                const color = e.__running ? 'var(--color-primary)' : (proj ? proj.color : 'var(--text-faint)');
                const width = 100 / (seg.cols || 1);
                const compact = (seg.endMin - seg.startMin) < 40;
                return (
                  <div
                    key={e.id + '_' + si}
                    className={`trk-cal-block ${e.__running ? 'is-running' : ''} ${compact ? 'is-compact' : ''} ${!seg.isStart ? 'no-top' : ''} ${!seg.isEnd ? 'no-bottom' : ''}`}
                    style={{
                      top: (seg.startMin / 60) * TT_HOUR_PX,
                      height: Math.max(((seg.endMin - seg.startMin) / 60) * TT_HOUR_PX, 14),
                      left: `${seg.col * width}%`,
                      width: `calc(${width}% - 3px)`,
                      '--trk-block-color': color,
                    }}
                    onPointerDown={(ev) => { if (seg.isStart && seg.isEnd) startMove(ev, e, dayStart); }}
                    onClick={(ev) => {
                      ev.stopPropagation();
                      if (eatClickRef.current || e.__running) return;
                      const t = tasksById.get(e.taskId);
                      if (t) onEditTask(t);
                    }}
                    title={`${desc || '(no description)'} · ${ttFmtClock(e.startedAt)} – ${e.__running ? 'now' : ttFmtClock(e.stoppedAt)}`}
                  >
                    {seg.isStart && seg.isEnd && !e.__running && (
                      <div className="trk-cal-handle is-top" onPointerDown={(ev) => startResize(ev, e, dayStart, 'top')}></div>
                    )}
                    <div className="trk-cal-block-desc">
                      {e.__running && <span className="trk-widget-pulse"></span>}
                      {desc || '(no description)'}
                    </div>
                    {!compact && (
                      <div className="trk-cal-block-meta">
                        {ttFmtClock(e.startedAt)} – {e.__running ? 'now' : ttFmtClock(e.stoppedAt)}
                      </div>
                    )}
                    {seg.isStart && seg.isEnd && !e.__running && (
                      <div className="trk-cal-handle is-bottom" onPointerDown={(ev) => startResize(ev, e, dayStart, 'bottom')}></div>
                    )}
                  </div>
                );
              })}
            </div>
          ))}
        </div>
      </div>

      <div className="trk-cal-hint">
        <Icon name="info"></Icon>
        Drag on empty space to create an entry · drag a block to move it · drag its edges to resize
      </div>
    </div>
  );
}

window.TtCalendarView = TtCalendarView;
