/* global React, Icon, IconBtn, db */
/* global ttFmtDuration, ttFmtClock, ttFmtDayLabel, ttStartOfDay, ttDateInputVal, ttTimeInputVal, ttParseDateTime, ttProjectById */
/* global ttSessionMs, ttTaskMs, ttResumeTask, ttDeleteTask, ttSetTaskArchived, ttEnsureProject */
/* global TtProjectDot, TtProjectPicker, TtTagPicker, TtCyclePicker */
const { useState, useEffect, useMemo, useRef } = React;

// ============================================================
// TtTaskModal — the task detail view: edit the task's fields and
// manage ALL of its tracking sessions. This is where a task's true
// total (across every session) is shown, and where individual
// sessions can be added, edited, or removed.
//
// Create mode (no task) builds a new task with one starter session.
//
// Each session has an explicit start date+time and end date+time, so a
// session can span any number of days (set the end date forward) — no
// implicit midnight rollover.
//
// Session validation:
//  - the end (date+time) must be strictly after the start; equal is invalid
//  - a session cannot overlap a currently running timer
//  - sessions MAY overlap each other (parallel work is the user's call)
// ============================================================

// Parse a human duration string → ms. Accepts "1:30", "1h 30m", "90m", "1.5h", "45"
function ttParseDuration(str) {
  const s = (str || '').trim().toLowerCase();
  if (!s) return null;
  let m;
  if ((m = s.match(/^(\d+):(\d{1,2})(?::(\d{1,2}))?$/))) {
    return ((+m[1]) * 3600 + (+m[2]) * 60 + (+(m[3] || 0))) * 1000;
  }
  if ((m = s.match(/^(?:(\d+(?:\.\d+)?)\s*h)?\s*(?:(\d+)\s*m)?$/)) && (m[1] || m[2])) {
    return ((+(m[1] || 0)) * 3600 + (+(m[2] || 0)) * 60) * 1000;
  }
  if ((m = s.match(/^(\d+(?:\.\d+)?)$/))) {
    return (+m[1]) * 60 * 1000; // bare number = minutes
  }
  return null;
}

// Local time helpers with SECOND precision — tracked sessions can be only
// seconds long, so minute-granularity inputs would collapse start==end.
function ttPad2(n) { return String(n).padStart(2, '0'); }
function ttTimeValS(ts) {
  const d = new Date(ts);
  return `${ttPad2(d.getHours())}:${ttPad2(d.getMinutes())}:${ttPad2(d.getSeconds())}`;
}
function ttParseDT(dateStr, timeStr) {
  if (!dateStr || !timeStr) return null;
  const [y, mo, da] = dateStr.split('-').map(Number);
  const [h, mi, se = 0] = timeStr.split(':').map(Number);
  return new Date(y, mo - 1, da, h, mi, se || 0, 0).getTime();
}

// Compute a session's timestamps from explicit start/end date+time strings.
// No implicit rollover: if the end isn't after the start, it's invalid and
// the caller must surface an error. spanDays = whole days the session covers.
function ttComputeSession(startDateStr, startStr, endDateStr, endStr) {
  const startedAt = ttParseDT(startDateStr, startStr);
  const stoppedAt = ttParseDT(endDateStr || startDateStr, endStr);
  if (startedAt == null || stoppedAt == null) return { startedAt: null, stoppedAt: null, spanDays: 0 };
  const spanDays = Math.max(0, Math.round((ttStartOfDay(stoppedAt) - ttStartOfDay(startedAt)) / 86400000));
  return { startedAt, stoppedAt, spanDays };
}

let ttDraftSeq = 0;
function ttMakeDraft(startedAt, stoppedAt, id) {
  return {
    key: id || ('new_' + (ttDraftSeq++)),
    id: id || null,
    startDateStr: ttDateInputVal(startedAt),
    startStr: ttTimeValS(startedAt),
    endDateStr: ttDateInputVal(stoppedAt),
    endStr: ttTimeValS(stoppedAt),
  };
}

// ------------------------------------------------------------
// One editable session row
// ------------------------------------------------------------
function TtSessionRow({ draft, onChange, onRemove, canRemove }) {
  const [durStr, setDurStr] = useState('');
  const [durFocused, setDurFocused] = useState(false);
  const [forceMulti, setForceMulti] = useState(false);
  const { startedAt, stoppedAt, spanDays } = ttComputeSession(draft.startDateStr, draft.startStr, draft.endDateStr, draft.endStr);
  const valid = startedAt != null && stoppedAt != null && stoppedAt > startedAt;
  const durationMs = valid ? stoppedAt - startedAt : 0;
  // Show a separate end-date field only when the session actually spans days,
  // or the user explicitly asked for it. Same-day = a single date field.
  const datesDiffer = draft.startDateStr !== draft.endDateStr;
  const showEndDate = forceMulti || datesDiffer;

  useEffect(() => {
    if (!durFocused) setDurStr(valid ? ttFmtDuration(durationMs) : '—');
  }, [durationMs, durFocused, valid]);

  const applyDuration = () => {
    setDurFocused(false);
    const ms = ttParseDuration(durStr);
    if (ms == null || ms <= 0 || startedAt == null) { setDurStr(valid ? ttFmtDuration(durationMs) : '—'); return; }
    const end = startedAt + ms;
    onChange({ ...draft, endDateStr: ttDateInputVal(end), endStr: ttTimeValS(end) });
  };

  // Same-day: the single date drives both start and end day.
  const onStartDate = (v) => onChange({ ...draft, startDateStr: v, endDateStr: showEndDate ? draft.endDateStr : v });
  const toggleMulti = () => {
    if (showEndDate) {
      // collapse back to one day: pin end date to the start date
      setForceMulti(false);
      if (datesDiffer) onChange({ ...draft, endDateStr: draft.startDateStr });
    } else {
      setForceMulti(true);
    }
  };

  return (
    <div className={`trk-session-edit ${!valid ? 'is-invalid' : ''}`}>
      <div className="trk-se-group">
        <input type="date" className="lh-input" value={draft.startDateStr}
          onChange={e => onStartDate(e.target.value)} />
        <input type="time" step="1" className="lh-input" value={draft.startStr}
          onChange={e => onChange({ ...draft, startStr: e.target.value })} />
      </div>
      <span className="trk-se-dash">–</span>
      <div className="trk-se-group">
        {showEndDate && (
          <input type="date" className="lh-input" value={draft.endDateStr}
            onChange={e => onChange({ ...draft, endDateStr: e.target.value })} />
        )}
        <input type="time" step="1" className="lh-input" value={draft.endStr}
          onChange={e => onChange({ ...draft, endStr: e.target.value })} />
        {spanDays > 0 && <span className="trk-nextday trk-se-nextday">+{spanDays}d</span>}
      </div>
      <button
        type="button"
        className={`trk-se-multi ${showEndDate ? 'is-on' : ''}`}
        title={showEndDate ? 'Collapse to a single day' : 'Session spans multiple days'}
        onClick={toggleMulti}
      >
        <Icon name="date_range"></Icon>
      </button>
      <input
        className="lh-input trk-se-dur"
        value={durStr}
        onFocus={() => setDurFocused(true)}
        onChange={e => setDurStr(e.target.value)}
        onBlur={applyDuration}
        onKeyDown={e => { if (e.key === 'Enter') e.currentTarget.blur(); }}
        title='Type a duration like "1:30", "90m" or "1h 15m" — end follows'
      />
      <IconBtn name="close" title="Remove session" onClick={onRemove} disabled={!canRemove}></IconBtn>
    </div>
  );
}

function TtTaskModal({ task, defaults = {}, projects, cycles, todos, knownTags, runningList = [], onClose }) {
  const isEdit = !!task;
  const base = task || {};
  // Is THIS task currently running? (any concurrent timer for it)
  const taskRunning = isEdit ? runningList.find(r => r.taskId === task.id) : null;

  const [description, setDescription] = useState(base.description ?? defaults.description ?? '');
  const [projectId, setProjectId] = useState(base.projectId ?? defaults.projectId ?? null);
  const [cycleId, setCycleId] = useState(base.cycleId ?? defaults.cycleId ?? null);
  const [tags, setTags] = useState(base.tags ?? defaults.tags ?? []);
  const [todoId, setTodoId] = useState(base.todoId ?? defaults.todoId ?? null);
  const [notes, setNotes] = useState(base.notes ?? '');
  const archived = !!base.archived;
  const [drafts, setDrafts] = useState([]);
  const [removedIds, setRemovedIds] = useState([]);
  const [loaded, setLoaded] = useState(false);
  const [error, setError] = useState(null);
  const descInputRef = useRef(null);

  // Load the task's sessions once (create mode seeds one starter session).
  useEffect(() => {
    let alive = true;
    (async () => {
      if (isEdit) {
        const sessions = await db.timeEntries.list({ where: s => s.taskId === task.id });
        sessions.sort((a, b) => b.startedAt - a.startedAt);
        if (!alive) return;
        setDrafts(sessions.map(s => ttMakeDraft(s.startedAt, s.stoppedAt, s.id)));
      } else {
        const start = defaults.startedAt ?? (Date.now() - 30 * 60000);
        const stop = defaults.stoppedAt ?? (start + 30 * 60000);
        setDrafts([ttMakeDraft(start, stop)]);
      }
      setLoaded(true);
    })();
    return () => { alive = false; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => { setTimeout(() => descInputRef.current?.focus(), 30); }, []);

  // Close on Escape
  useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') { e.stopPropagation(); onClose(); } };
    document.addEventListener('keydown', onKey, true);
    return () => document.removeEventListener('keydown', onKey, true);
  }, [onClose]);

  // Live total across the drafted sessions
  const totalMs = useMemo(() => drafts.reduce((a, d) => {
    const c = ttComputeSession(d.startDateStr, d.startStr, d.endDateStr, d.endStr);
    return a + (c.startedAt != null && c.stoppedAt > c.startedAt ? c.stoppedAt - c.startedAt : 0);
  }, 0), [drafts]);

  const updateDraft = (key, next) => setDrafts(ds => ds.map(d => d.key === key ? next : d));
  const removeDraft = (d) => {
    setDrafts(ds => ds.filter(x => x.key !== d.key));
    if (d.id) setRemovedIds(ids => [...ids, d.id]);
  };
  const addSession = () => {
    const start = Date.now() - 30 * 60000;
    setDrafts(ds => [ttMakeDraft(start, start + 30 * 60000), ...ds]);
  };

  const linkedTodo = todos.find(t => t.id === todoId);
  const todoOptions = useMemo(
    () => todos.filter(t => !t.done || t.id === todoId),
    [todos, todoId]
  );

  const save = async () => {
    setError(null);
    // Compute + validate every session
    const computed = [];
    for (const d of drafts) {
      const c = ttComputeSession(d.startDateStr, d.startStr, d.endDateStr, d.endStr);
      if (c.startedAt == null) { setError('Every session needs a start and end date and time.'); return; }
      if (c.stoppedAt <= c.startedAt) { setError('A session’s end must be after its start (end the next day if it runs overnight).'); return; }
      for (const run of runningList) {
        const runEnd = Date.now();
        if (c.startedAt < runEnd && c.stoppedAt > run.startedAt) {
          setError(`A session overlaps a running timer (started ${ttTimeInputVal(run.startedAt)}). Stop it first, or adjust the times.`);
          return;
        }
      }
      computed.push({ id: d.id, startedAt: c.startedAt, stoppedAt: c.stoppedAt });
    }

    const lastActiveAt = computed.length ? Math.max(...computed.map(c => c.stoppedAt)) : Date.now();
    const savedTask = await db.timeTasks.put({
      ...(isEdit ? task : {}),
      description: description.trim(),
      projectId: projectId || null,
      cycleId: cycleId || null,
      tags,
      todoId: todoId || null,
      notes: notes.trim(),
      archived: isEdit ? !!task.archived : false,
      lastActiveAt,
    });

    for (const id of removedIds) await db.timeEntries.delete(id);
    for (const c of computed) {
      await db.timeEntries.put({
        ...(c.id ? { id: c.id } : {}),
        taskId: savedTask.id,
        startedAt: c.startedAt,
        stoppedAt: c.stoppedAt,
      });
    }
    onClose();
  };

  const removeTask = async () => {
    const ok = await window.lhDialog.confirm({
      title: 'Delete task?',
      message: `“${description || '(no description)'}” and all ${drafts.length} ${drafts.length === 1 ? 'session' : 'sessions'} (${ttFmtDuration(totalMs)}) will be removed.`,
      confirmLabel: 'Delete', danger: true, icon: 'delete',
    });
    if (!ok) return;
    await ttDeleteTask(task.id);
    onClose();
  };

  const resumeNow = async () => {
    await ttResumeTask(task.id);
    onClose();
  };

  const toggleArchive = async () => {
    await ttSetTaskArchived(task.id, !archived);
    onClose();
  };

  return (
    <div className="trk-modal-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="trk-modal" data-screen-label={isEdit ? 'Task detail' : 'New task'}>
        <div className="trk-modal-head">
          <Icon name={isEdit ? 'task_alt' : 'more_time'}></Icon>
          <span>{isEdit ? 'Task detail' : 'New task'}</span>
          {archived && <span className="trk-archived-badge">Archived</span>}
          <div style={{ flex: 1 }}></div>
          {isEdit && !taskRunning && (
            <button className="lh-btn ghost trk-modal-resume" onClick={resumeNow} title="Start a new session now">
              <Icon name="play_arrow"></Icon>Resume
            </button>
          )}
          <IconBtn name="close" title="Close" onClick={onClose}></IconBtn>
        </div>

        <div className="trk-modal-body">
          <input
            ref={descInputRef}
            className="lh-input trk-modal-desc"
            placeholder="What is this task?"
            value={description}
            onChange={e => setDescription(e.target.value)}
            maxLength={200}
          />

          <div className="trk-modal-row">
            <TtProjectPicker projects={projects} value={projectId} onChange={setProjectId}></TtProjectPicker>
            <TtCyclePicker cycles={cycles} value={cycleId} onChange={setCycleId}></TtCyclePicker>
            <TtTagPicker value={tags} onChange={setTags} knownTags={knownTags}></TtTagPicker>
          </div>

          <div className="trk-modal-field">
            <label>Linked todo</label>
            <select
              className="lh-input"
              value={todoId || ''}
              onChange={e => setTodoId(e.target.value || null)}
            >
              <option value="">Not linked</option>
              {todoOptions.map(t => (
                <option key={t.id} value={t.id}>{t.title}{t.done ? ' (done)' : ''}</option>
              ))}
            </select>
            {linkedTodo && linkedTodo.project && !projectId && (
              <button
                className="lh-chip trk-modal-hint-chip"
                onClick={async () => {
                  const p = await ttEnsureProject(linkedTodo.project);
                  if (p) setProjectId(p.id);
                }}
              >
                <Icon name="folder"></Icon>Use “{linkedTodo.project}” from the todo
              </button>
            )}
          </div>

          <div className="trk-modal-sessions">
            <div className="trk-modal-sessions-head">
              <label>
                Sessions
                <span className="trk-modal-sessions-count">{drafts.length}</span>
              </label>
              <span className="trk-modal-sessions-total">{ttFmtDuration(totalMs, { seconds: false })} total</span>
              <button className="lh-chip" onClick={addSession}>
                <Icon name="add"></Icon>Add session
              </button>
            </div>
            {taskRunning && (
              <div className="trk-modal-running-note">
                <span className="trk-widget-pulse"></span>
                A timer is running for this task right now — it’ll become a session when you stop it.
              </div>
            )}
            <div className="trk-modal-sessions-list">
              {loaded && drafts.length === 0 && (
                <div className="trk-modal-sessions-empty">No sessions yet — add one above.</div>
              )}
              {drafts.map(d => (
                <TtSessionRow
                  key={d.key}
                  draft={d}
                  canRemove={drafts.length > 1 || !isEdit}
                  onChange={(next) => updateDraft(d.key, next)}
                  onRemove={() => removeDraft(d)}
                ></TtSessionRow>
              ))}
            </div>
          </div>

          <div className="trk-modal-field">
            <label>Notes</label>
            <textarea
              className="lh-input"
              rows={2}
              placeholder="Optional context…"
              value={notes}
              onChange={e => setNotes(e.target.value)}
            ></textarea>
          </div>

          {error && (
            <div className="trk-modal-error">
              <Icon name="error_outline"></Icon>
              <span>{error}</span>
            </div>
          )}
        </div>

        <div className="trk-modal-foot">
          {isEdit && (
            <button className="lh-btn danger ghost" onClick={removeTask}>
              <Icon name="delete"></Icon>Delete task
            </button>
          )}
          {isEdit && (
            <button className="lh-btn ghost" onClick={toggleArchive}>
              <Icon name={archived ? 'unarchive' : 'inventory_2'}></Icon>{archived ? 'Restore' : 'Archive'}
            </button>
          )}
          <div style={{ flex: 1 }}></div>
          <button className="lh-btn ghost" onClick={onClose}>Cancel</button>
          <button className="lh-btn primary" onClick={save}>
            <Icon name="check"></Icon>{isEdit ? 'Save changes' : 'Create task'}
          </button>
        </div>
      </div>
    </div>
  );
}

window.TtTaskModal = TtTaskModal;
