import { useEffect, useMemo, useRef, useState } from "react";
import type React from "react";
import {
  Activity,
  BarChart3,
  Download,
  FileClock,
  FileJson,
  Github,
  HelpCircle,
  Heart,
  Search,
  Plus,
  RotateCcw,
  Save,
  Send,
  ShieldAlert,
  Trash2,
  Upload,
  X,
  UserRound
} from "lucide-react";
import {
  accommodationLabels,
  categoryTooltipTexts,
  delayedSymptomLabels,
  limitationLabels,
  negativeScale,
  positiveScale,
  recoveryLabels,
  statusLabels,
  taskCompletionLabels,
  taskTypeLabels,
  tooltipTexts,
  worseLabels
} from "./labels";
import {
  deleteEvaluatorNote,
  deleteEvaluatorProfile,
  ensureDemoEvaluatorProfiles,
  exportEvaluatorBackup,
  importEvaluatorBackup,
  importExportPayload,
  listEvaluatorProfiles,
  renameEvaluatorProfile,
  upsertEvaluatorNote
} from "./evaluatorStorage";
import {
  calculatePeriodWorkAbilityAssessment,
  calculateReportScore,
  getEntryTime,
  getReportScore,
  getScoreBand,
  normalizeReportMetric,
  scoreBandDisclaimer
} from "./scoring";
import { verificationLabel, verificationTooltip } from "./integrity";
import {
  I18nProvider,
  translateScoreBand,
  translateScoreBandDescription,
  translateVerificationStatus,
  useI18n
} from "./i18n";
import {
  confirmSecureShareImport,
  createSecureShare,
  decryptSecureSharePayload,
  fetchSecureShare,
  parseShareRequestFromLocation
} from "./secureShare";
import {
  createFollowup,
  createReport,
  deleteAllReports,
  deleteReport,
  assertValidExportRange,
  buildExportFilename,
  ensureDemoData,
  exportReports,
  getLatestWorkReport,
  getLastWorkReport,
  importReportsFromJson,
  isEditableWithin24Hours,
  isDemoModeEnabled,
  listReports,
  previewImportReports,
  setDemoModeEnabled,
  updateFollowup,
  updateReport
} from "./storage";
import type { ExportRange } from "./storage";
import type {
  Accommodation,
  DelayedFollowup,
  DelayedSymptom,
  EvaluatorProfile,
  FollowupDraft,
  ImportSummary,
  Limitation,
  RecoveryStatus,
  Report,
  ReportDraft,
  ReportStatus,
  ScaleValue,
  StoredEntry,
  TaskCompletion,
  TaskType,
  WorseThanBefore
} from "./types";

type View = "registrer" | "oppfolging" | "oversikt" | "personvern";
type PresetRange = "7d" | "1m" | "3m" | "6m" | "1y" | "3y" | "5y" | "all" | "custom";
type LineVisibility = "normal" | "dim" | "hidden";
type TrendMetric = { key: keyof Report | "total_score_percent"; label: string; short: string; color: string };

const PERIOD_KEY = "arbeidsevne-egenvurdering:trend-period";
const APP_VERSION = "1.2";

const taskTypeOptions = Object.keys(taskTypeLabels) as TaskType[];
const limitationOptions = Object.keys(limitationLabels) as Limitation[];
const accommodationOptions = Object.keys(accommodationLabels) as Accommodation[];
const delayedSymptomOptions = Object.keys(delayedSymptomLabels) as DelayedSymptom[];
const visibleStatusOptions: ReportStatus[] = ["kan_fortsette", "trenger_pause", "trenger_enklere_oppgave", "bør_stoppe"];
const trendMetrics: TrendMetric[] = [
  { key: "total_score_percent", label: "Totalscore", short: "T", color: "#235b5e" },
  { key: "work_ability", label: "Arbeidsevne", short: "A", color: "#307f78" },
  { key: "energy_level", label: "Energi", short: "E", color: "#b2642a" },
  { key: "mental_clarity", label: "Klarhet", short: "K", color: "#4f5c9c" },
  { key: "symptom_burden", label: "Symptomer", short: "S", color: "#b23b4a" },
  { key: "effort_strain", label: "Belastning", short: "B", color: "#6b5a3a" }
];

function App() {
  return (
    <I18nProvider>
      <AppRoutes />
    </I18nProvider>
  );
}

function AppRoutes() {
  const { t } = useI18n();
  if (window.location.pathname.startsWith("/s")) {
    return <SecureSharePage />;
  }
  if (window.location.pathname.startsWith("/evaluator") || window.location.pathname.startsWith("/veileder")) {
    return <EvaluatorApp />;
  }

  const [demoMode, setDemoMode] = useState(() => isDemoModeEnabled());
  const [view, setView] = useState<View>("registrer");
  const [entries, setEntries] = useState<StoredEntry[]>(() => listReports(isDemoModeEnabled()));
  const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
  const [deleteRequest, setDeleteRequest] = useState<{ id: string; name: string } | null>(null);
  const [editEntry, setEditEntry] = useState<StoredEntry | null>(null);

  const refresh = () => {
    const next = listReports(demoMode);
    setEntries(next);
  };
  const selectedEntry = selectedEntryId ? entries.find((entry) => entry.id === selectedEntryId) : undefined;
  const changeDemoMode = (enabled: boolean) => {
    setDemoModeEnabled(enabled);
    if (enabled) ensureDemoData();
    setDemoMode(enabled);
    setSelectedEntryId(null);
    setDeleteRequest(null);
    setEditEntry(null);
    setEntries(listReports(enabled));
  };

  useEffect(() => {
    setEntries(listReports(demoMode));
  }, [demoMode]);

  return (
    <div className={demoMode ? "app-shell demo-active" : "app-shell"}>
      <header className="topbar">
        <div>
          <p className="eyebrow">{t("app.eyebrow")}</p>
          <h1>{t("app.title")}</h1>
        </div>
        <a className="text-link" href="/veileder">
          {t("app.evaluator")}
        </a>
      </header>

      <nav className="main-nav" aria-label={t("app.navLabel")}>
        <NavButton icon={<Plus />} label={t("nav.register")} active={view === "registrer"} onClick={() => setView("registrer")} />
        <NavButton
          icon={<FileClock />}
          label={t("nav.followup")}
          active={view === "oppfolging"}
          onClick={() => setView("oppfolging")}
        />
        <NavButton icon={<BarChart3 />} label={t("nav.overview")} active={view === "oversikt"} onClick={() => setView("oversikt")} />
        <NavButton
          icon={<ShieldAlert />}
          label={t("nav.privacy")}
          active={view === "personvern"}
          onClick={() => setView("personvern")}
        />
      </nav>

      <main>
        {demoMode && (
          <div className="notice warning">
            <ShieldAlert aria-hidden="true" />
            <span>{t("demo.notice")}</span>
          </div>
        )}
        {view === "registrer" && (
          <ReportForm
            demoMode={demoMode}
            editReport={editEntry?.type === "work_report" ? editEntry : null}
            onCancelEdit={() => setEditEntry(null)}
            onSaved={() => {
              setEditEntry(null);
              refresh();
            }}
          />
        )}
        {view === "oppfolging" && (
          <FollowupForm
            demoMode={demoMode}
            editFollowup={editEntry?.type === "delayed_followup" ? editEntry : null}
            reports={entries}
            onCancelEdit={() => setEditEntry(null)}
            onSaved={() => {
              setEditEntry(null);
              refresh();
            }}
          />
        )}
        {view === "oversikt" && (
          <Overview
            entries={entries}
            selectedEntry={selectedEntry}
            onSelect={setSelectedEntryId}
            onClose={() => setSelectedEntryId(null)}
            onRequestEdit={(entry) => {
              setEditEntry(entry);
              setView(entry.type === "work_report" ? "registrer" : "oppfolging");
            }}
            onRequestDelete={(entry) => setDeleteRequest({ id: entry.id, name: entry.type === "work_report" ? entry.workplace || t("danger.reportName") : t("danger.followupName") })}
          />
        )}
        {view === "personvern" && <PrivacyPanel demoMode={demoMode} entries={entries} onDeleted={refresh} />}
      </main>
      <AppFooter demoMode={demoMode} onDemoModeChange={changeDemoMode} />
      <ConfirmDeleteModal
        itemName={deleteRequest?.name ?? ""}
        open={Boolean(deleteRequest)}
        onCancel={() => setDeleteRequest(null)}
        onConfirm={() => {
          if (!deleteRequest) return;
          deleteReport(deleteRequest.id, demoMode);
          if (selectedEntryId === deleteRequest.id) setSelectedEntryId(null);
          setDeleteRequest(null);
          refresh();
        }}
      />
    </div>
  );
}

function NavButton({
  icon,
  label,
  active,
  onClick
}: {
  icon: React.ReactNode;
  label: string;
  active: boolean;
  onClick: () => void;
}) {
  return (
    <button className={active ? "nav-button active" : "nav-button"} onClick={onClick} type="button">
      {icon}
      <span>{label}</span>
    </button>
  );
}

function AppFooter({
  demoMode,
  onDemoModeChange,
  showDemoToggle = true
}: {
  demoMode: boolean;
  onDemoModeChange: (enabled: boolean) => void;
  showDemoToggle?: boolean;
}) {
  const { language, setLanguage, t } = useI18n();
  const baseYear = 2026;
  const currentYear = new Date().getFullYear();
  const yearLabel = currentYear > baseYear ? `${baseYear}-${currentYear}` : `${baseYear}`;
  return (
    <footer className="app-footer">
      <div className="footer-language-switcher" aria-label={t("footer.languageChoice")}>
        <button
          aria-current={language === "nb-NO"}
          aria-label={t("footer.switchToNorwegian")}
          className="footer-language-button"
          onClick={() => setLanguage("nb-NO")}
          type="button"
        >
          {t("footer.norwegian")} 🇳🇴
        </button>
        <span className="footer-language-separator">|</span>
        <button
          aria-current={language === "en"}
          aria-label={t("footer.switchToEnglish")}
          className="footer-language-button"
          onClick={() => setLanguage("en")}
          type="button"
        >
          {t("footer.english")} 🇬🇧
        </button>
      </div>
      <div className="footer-credit">
        © {yearLabel} {t("footer.developedBy")} <Heart aria-label={t("footer.heart")} className="footer-heart" />
      </div>
      <div className="footer-links">
        <a className="footer-link" href="https://git.rolfsvaag.no/frarol96/Arbeidspuls" rel="noreferrer" target="_blank">
          <Github aria-hidden="true" />
          <span>{t("footer.sourceCode", { version: APP_VERSION })}</span>
        </a>
        {showDemoToggle && (
          <button className={demoMode ? "footer-link active" : "footer-link"} onClick={() => onDemoModeChange(!demoMode)} type="button">
            {demoMode ? t("demo.exit") : t("demo.enter")}
          </button>
        )}
      </div>
    </footer>
  );
}

function ConfirmDeleteModal({
  open,
  itemName,
  onCancel,
  onConfirm
}: {
  open: boolean;
  itemName: string;
  onCancel: () => void;
  onConfirm: () => void;
}) {
  const { t } = useI18n();
  const [secondsLeft, setSecondsLeft] = useState(1);

  useEffect(() => {
    if (!open) return;
    setSecondsLeft(1);
    const timer = window.setTimeout(() => setSecondsLeft(0), 1000);
    return () => window.clearTimeout(timer);
  }, [open, itemName]);

  useEffect(() => {
    if (!open) return;
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") onCancel();
      if (event.key === "Enter" && secondsLeft > 0) event.preventDefault();
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [onCancel, open, secondsLeft]);

  if (!open) return null;

  return (
    <div className="modal-overlay" role="presentation" onMouseDown={onCancel}>
      <section className="confirm-modal" role="dialog" aria-modal="true" aria-labelledby="confirm-title" onMouseDown={(event) => event.stopPropagation()}>
        <h2 id="confirm-title">{t("deleteModal.title")}</h2>
        <p>{t("deleteModal.body", { itemName })}</p>
        <div className="action-grid">
          <button className="secondary-action" onClick={onCancel} type="button">
            {t("common.cancel")}
          </button>
          <button className="secondary-action danger" disabled={secondsLeft > 0} onClick={onConfirm} type="button">
            {secondsLeft > 0 ? t("deleteModal.waiting", { secondsLeft }) : t("deleteModal.ready")}
          </button>
        </div>
      </section>
    </div>
  );
}

function createInitialReportDraft(demoMode = false): ReportDraft {
  const last = getLastWorkReport(demoMode);
  return {
    workplace: last?.workplace ?? "",
    work_start_time: last?.work_start_time ?? "",
    work_end_time: last?.work_end_time ?? "",
    task_types: [],
    main_limitations: [],
    helpful_accommodations: [],
    note: ""
  };
}

function createReportDraftFromReport(report: Report): ReportDraft {
  return {
    workplace: report.workplace ?? "",
    work_start_time: report.work_start_time ?? "",
    work_end_time: report.work_end_time ?? "",
    work_ability: report.work_ability,
    energy_level: report.energy_level,
    mental_clarity: report.mental_clarity,
    symptom_burden: report.symptom_burden,
    effort_strain: report.effort_strain,
    status: report.status,
    physical_energy_detail: report.physical_energy,
    mental_energy_detail: report.mental_energy,
    perceived_productivity: report.perceived_productivity,
    task_completion: report.task_completion,
    task_types: report.task_types,
    main_limitations: report.main_limitations,
    helpful_accommodations: report.helpful_accommodations,
    note: report.note ?? ""
  };
}

function ReportForm({
  demoMode,
  editReport,
  onCancelEdit,
  onSaved
}: {
  demoMode: boolean;
  editReport?: Report | null;
  onCancelEdit?: () => void;
  onSaved: () => void;
}) {
  const { labels, t } = useI18n();
  const editing = Boolean(editReport);
  const editAllowed = !editReport || isEditableWithin24Hours(editReport);
  const [draft, setDraft] = useState<ReportDraft>(() => editReport ? createReportDraftFromReport(editReport) : createInitialReportDraft(demoMode));
  const [attempted, setAttempted] = useState(false);
  const [savedText, setSavedText] = useState("");

  const reportFields = requiredReportFields.map((field) => ({ ...field, label: reportFieldLabel(field.key, t) }));
  const missing = reportFields.filter((field) => !draft[field.key]);
  const canSubmit = missing.length === 0;

  useEffect(() => {
    setDraft(editReport ? createReportDraftFromReport(editReport) : createInitialReportDraft(demoMode));
    setAttempted(false);
    setSavedText("");
  }, [demoMode, editReport?.id]);

  const setScale = (key: keyof ReportDraft, value: ScaleValue) => {
    setDraft((current) => ({ ...current, [key]: value }));
  };

  const toggle = <T extends string>(key: keyof ReportDraft, value: T) => {
    setDraft((current) => {
      const values = current[key] as T[];
      return {
        ...current,
        [key]: values.includes(value) ? values.filter((item) => item !== value) : [...values, value]
      };
    });
  };

  const submit = () => {
    setAttempted(true);
    if (!canSubmit || !editAllowed) return;

    if (editReport) updateReport(editReport.id, draft, demoMode);
    else createReport(draft, demoMode);
    setDraft(createInitialReportDraft(demoMode));
    setAttempted(false);
    setSavedText(editReport ? t("edit.saved") : t("register.saved"));
    onSaved();
    window.setTimeout(() => setSavedText(""), 2400);
  };

  return (
    <section className="flow">
      <div className="notice">
        <Activity aria-hidden="true" />
        <span>{editing ? t("edit.reportIntro") : t("register.intro")}</span>
      </div>
      {editing && !editAllowed && <p className="error-text">{t("edit.expired")}</p>}

      <section className="field-card compact-session">
        <div className="field-head inline-head">
          <h2>{t("register.workSession")}</h2>
          <Tooltip text={`${labels.tooltipTexts.workplace} ${labels.tooltipTexts.work_time}`} />
        </div>
        <div className="session-grid">
          <label className="text-field">
            <span>{t("register.workplace")}</span>
            <input
              value={draft.workplace ?? ""}
              onChange={(event) => setDraft((current) => ({ ...current, workplace: event.target.value }))}
              placeholder={t("register.optional")}
            />
          </label>
          <label className="text-field">
            <span>{t("register.startTime")}</span>
            <input
              type="time"
              value={draft.work_start_time ?? ""}
              onChange={(event) => setDraft((current) => ({ ...current, work_start_time: event.target.value }))}
            />
          </label>
          <label className="text-field">
            <span>{t("register.endTime")}</span>
            <input
              type="time"
              value={draft.work_end_time ?? ""}
              onChange={(event) => setDraft((current) => ({ ...current, work_end_time: event.target.value }))}
            />
          </label>
        </div>
      </section>

      <div className="form-stack">
        {reportFields.map((field) => (
          <ScaleField
            key={field.key}
            label={field.label}
            tooltip={labels.tooltipTexts[field.key]}
            value={draft[field.key] as ScaleValue | undefined}
            guidance={field.negative ? labels.negativeScale : labels.positiveScale}
            invalid={attempted && !draft[field.key]}
            onChange={(value) => setScale(field.key, value)}
          />
        ))}

        <section className={attempted && !draft.status ? "field-card invalid" : "field-card"}>
          <div className="field-head">
            <h2>{t("register.continueQuestion")}</h2>
            <p>{t("register.chooseBestNow")}</p>
          </div>
          <div className="status-grid">
            {visibleStatusOptions.map((status) => (
              <button
                className={draft.status === status ? "status-button selected" : "status-button"}
                key={status}
                onClick={() => setDraft((current) => ({ ...current, status }))}
                type="button"
              >
                {labels.statusLabels[status]}
              </button>
            ))}
          </div>
        </section>

        <details className="details-panel">
          <summary>{t("register.optionalDetails")}</summary>
          <div className="details-content">
            <ScaleField
              label={t("register.physicalEnergy")}
              tooltip={labels.tooltipTexts.physical_energy}
              value={draft.physical_energy_detail}
              guidance={labels.positiveScale}
              compact
              onChange={(value) => setScale("physical_energy_detail", value)}
            />
            <ScaleField
              label={t("register.mentalEnergy")}
              tooltip={labels.tooltipTexts.mental_energy}
              value={draft.mental_energy_detail}
              guidance={labels.positiveScale}
              compact
              onChange={(value) => setScale("mental_energy_detail", value)}
            />
            <ScaleField
              label={t("register.productivity")}
              value={draft.perceived_productivity}
              guidance={labels.positiveScale}
              compact
              onChange={(value) => setScale("perceived_productivity", value)}
            />
            <SelectField
              label={t("register.taskCompletion")}
              value={draft.task_completion}
              options={labels.taskCompletionLabels}
              onChange={(value) => setDraft((current) => ({ ...current, task_completion: value }))}
            />
            <ChipGroup
              label={t("register.taskTypes")}
              options={taskTypeOptions}
              labels={labels.taskTypeLabels}
              values={draft.task_types}
              onToggle={(value) => toggle("task_types", value)}
            />
            <ChipGroup
              label={t("register.limitations")}
              options={limitationOptions}
              labels={labels.limitationLabels}
              values={draft.main_limitations}
              onToggle={(value) => toggle("main_limitations", value)}
            />
            <ChipGroup
              label={t("register.accommodations")}
              options={accommodationOptions}
              labels={labels.accommodationLabels}
              values={draft.helpful_accommodations}
              onToggle={(value) => toggle("helpful_accommodations", value)}
            />
            <label className="text-field">
              <span>{t("register.note")}</span>
              <textarea
                maxLength={1000}
                value={draft.note}
                onChange={(event) => setDraft((current) => ({ ...current, note: event.target.value }))}
                rows={4}
              />
            </label>
          </div>
        </details>
      </div>

      <div className="sticky-actions">
        {attempted && !canSubmit && <p className="error-text">{t("register.missing", { fields: missing.map((field) => field.label).join(", ") })}</p>}
        {savedText && <p className="success-text">{savedText}</p>}
        <button className="primary-action" disabled={!editAllowed} onClick={submit} type="button">
          <Save aria-hidden="true" />
          <span>{editing ? t("edit.saveChanges") : t("register.saveReport")}</span>
        </button>
        {editing && onCancelEdit && (
          <button className="secondary-action" onClick={onCancelEdit} type="button">
            {t("edit.cancelEdit")}
          </button>
        )}
      </div>
    </section>
  );
}

const requiredReportFields: Array<{
  key: "work_ability" | "energy_level" | "mental_clarity" | "symptom_burden" | "effort_strain";
  label: string;
  negative?: boolean;
}> = [
  { key: "work_ability", label: "Arbeidsevne akkurat nå" },
  { key: "energy_level", label: "Energinivå" },
  { key: "mental_clarity", label: "Mental klarhet / konsentrasjon" },
  { key: "symptom_burden", label: "Symptombelastning", negative: true },
  { key: "effort_strain", label: "Anstrengelse / belastning", negative: true }
];

function reportFieldLabel(key: (typeof requiredReportFields)[number]["key"], t: ReturnType<typeof useI18n>["t"]) {
  const map: Record<typeof key, string> = {
    work_ability: t("register.workAbility"),
    energy_level: t("register.energyLevel"),
    mental_clarity: t("register.mentalClarity"),
    symptom_burden: t("register.symptomBurden"),
    effort_strain: t("register.effortStrain")
  };
  return map[key];
}

function ScaleField({
  label,
  value,
  guidance,
  tooltip,
  invalid,
  compact,
  onChange
}: {
  label: string;
  value?: ScaleValue;
  guidance: Record<ScaleValue, string>;
  tooltip?: string;
  invalid?: boolean;
  compact?: boolean;
  onChange: (value: ScaleValue) => void;
}) {
  const { t } = useI18n();
  return (
    <section className={`${compact ? "field-card compact" : "field-card"} ${invalid ? "invalid" : ""}`}>
      <div className="field-head">
        <div className="inline-head">
          <h2>{label}</h2>
          {tooltip && <Tooltip text={tooltip} />}
        </div>
        <p>
          {t("register.oneEqualsFiveEquals", { low: guidance[1].toLowerCase(), high: guidance[5].toLowerCase() })}
        </p>
      </div>
      <div className="scale-grid" role="group" aria-label={label}>
        {([1, 2, 3, 4, 5] as ScaleValue[]).map((number) => (
          <button
            className={value === number ? "scale-button selected" : "scale-button"}
            key={number}
            onClick={() => onChange(number)}
            type="button"
            aria-pressed={value === number}
            title={guidance[number]}
          >
            <strong>{number}</strong>
            <span>{guidance[number]}</span>
          </button>
        ))}
      </div>
    </section>
  );
}

function Tooltip({ text, label }: { text: string; label?: string }) {
  const { t } = useI18n();
  const [open, setOpen] = useState(false);

  useEffect(() => {
    if (!open) return;
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") setOpen(false);
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [open]);

  return (
    <span className={open ? "tooltip open" : "tooltip"} onMouseLeave={() => setOpen(false)}>
      <button
        className="tooltip-button"
        type="button"
        aria-label={label ?? t("overview.explainTrend")}
        aria-expanded={open}
        onClick={() => setOpen((current) => !current)}
        onFocus={() => setOpen(true)}
      >
        <HelpCircle aria-hidden="true" />
      </button>
      <span role="tooltip">{text}</span>
    </span>
  );
}

function SelectField<T extends string>({
  label,
  value,
  options,
  onChange
}: {
  label: string;
  value?: T;
  options: Record<T, string>;
  onChange: (value: T | undefined) => void;
}) {
  const { t } = useI18n();
  return (
    <label className="select-field">
      <span>{label}</span>
      <select value={value ?? ""} onChange={(event) => onChange((event.target.value || undefined) as T | undefined)}>
        <option value="">{t("common.notSelected")}</option>
        {(Object.keys(options) as T[]).map((key) => (
          <option key={key} value={key}>
            {options[key]}
          </option>
        ))}
      </select>
    </label>
  );
}

function ChipGroup<T extends string>({
  label,
  options,
  labels,
  values,
  onToggle
}: {
  label: string;
  options: T[];
  labels: Record<T, string>;
  values: T[];
  onToggle: (value: T) => void;
}) {
  return (
    <section className="chip-section">
      <h3>{label}</h3>
      <div className="chips">
        {options.map((option) => (
          <button
            className={values.includes(option) ? "chip selected" : "chip"}
            key={option}
            onClick={() => onToggle(option)}
            type="button"
            aria-pressed={values.includes(option)}
          >
            {labels[option]}
          </button>
        ))}
      </div>
    </section>
  );
}

function createFollowupDraftFromFollowup(followup: DelayedFollowup): FollowupDraft {
  return {
    parent_report_id: findParentId(followup),
    related_report_id: findParentId(followup),
    worse_than_before: followup.worse_than_before,
    delayed_symptoms: followup.delayed_symptoms,
    recovery_status: followup.recovery_status,
    note: followup.note ?? ""
  };
}

function FollowupForm({
  demoMode,
  editFollowup,
  reports,
  onCancelEdit,
  onSaved
}: {
  demoMode: boolean;
  editFollowup?: DelayedFollowup | null;
  reports: StoredEntry[];
  onCancelEdit?: () => void;
  onSaved: () => void;
}) {
  const { labels, t } = useI18n();
  const editing = Boolean(editFollowup);
  const editAllowed = !editFollowup || isEditableWithin24Hours(editFollowup);
  const [attempted, setAttempted] = useState(false);
  const [savedText, setSavedText] = useState("");
  const workReports = reports
    .filter((entry): entry is Report => entry.type === "work_report")
    .sort((a, b) => getEntryTime(b) - getEntryTime(a));
  const latestReport = workReports[0] ?? getLatestWorkReport(demoMode);
  const editParent = editFollowup ? workReports.find((report) => report.id === findParentId(editFollowup)) : undefined;
  const parentOptions = Object.fromEntries(
    [latestReport, editParent]
      .filter((report): report is Report => Boolean(report))
      .filter((report, index, array) => array.findIndex((candidate) => candidate.id === report.id) === index)
      .map((report) => [report.id, formatReportPickerLabel(report, labels, t)])
  );
  const [draft, setDraft] = useState<FollowupDraft>(() =>
    editFollowup
      ? createFollowupDraftFromFollowup(editFollowup)
      : { parent_report_id: latestReport?.id, related_report_id: latestReport?.id, delayed_symptoms: [], note: "" }
  );
  const selectedParentIsLatest =
    !draft.parent_report_id ||
    !latestReport ||
    draft.parent_report_id === latestReport.id ||
    Boolean(editFollowup && draft.parent_report_id === findParentId(editFollowup));

  useEffect(() => {
    setDraft(
      editFollowup
        ? createFollowupDraftFromFollowup(editFollowup)
        : { parent_report_id: latestReport?.id, related_report_id: latestReport?.id, delayed_symptoms: [], note: "" }
    );
    setAttempted(false);
    setSavedText("");
  }, [demoMode, editFollowup?.id, latestReport?.id]);

  const submit = () => {
    setAttempted(true);
    if (!draft.worse_than_before || !selectedParentIsLatest || !editAllowed) return;

    if (editFollowup) updateFollowup(editFollowup.id, draft, demoMode);
    else createFollowup(draft, demoMode);
    setDraft({ parent_report_id: latestReport?.id, related_report_id: latestReport?.id, delayed_symptoms: [], note: "" });
    setAttempted(false);
    setSavedText(editFollowup ? t("edit.saved") : t("followup.saved"));
    onSaved();
    window.setTimeout(() => setSavedText(""), 2400);
  };

  const toggleSymptom = (value: DelayedSymptom) => {
    setDraft((current) => ({
      ...current,
      delayed_symptoms: current.delayed_symptoms.includes(value)
        ? current.delayed_symptoms.filter((item) => item !== value)
        : [...current.delayed_symptoms, value]
    }));
  };

  return (
    <section className="flow">
      <div className="notice">
        <FileClock aria-hidden="true" />
        <span>{editing ? t("edit.followupIntro") : t("followup.intro")}</span>
      </div>
      {editing && !editAllowed && <p className="error-text">{t("edit.expired")}</p>}

      <section className={attempted && !draft.worse_than_before ? "field-card invalid" : "field-card"}>
        <div className="field-head">
          <h2>{t("followup.worseQuestion")}</h2>
          <p>{t("followup.chooseOne")}</p>
        </div>
        <div className="status-grid four">
          {(Object.keys(worseLabels) as WorseThanBefore[]).map((value) => (
            <button
              className={draft.worse_than_before === value ? "status-button selected" : "status-button"}
              key={value}
              onClick={() => setDraft((current) => ({ ...current, worse_than_before: value }))}
              type="button"
            >
              {labels.worseLabels[value]}
            </button>
          ))}
        </div>
      </section>

      <section className="field-card">
        <SelectField
          label={t("followup.connectReport")}
          value={draft.parent_report_id}
          options={parentOptions}
          onChange={(value) => setDraft((current) => ({ ...current, parent_report_id: value, related_report_id: value }))}
        />
        {attempted && !selectedParentIsLatest && <p className="error-text">{t("followup.latestOnly")}</p>}
      </section>

      <details className="details-panel">
        <summary>{t("register.optionalDetails")}</summary>
        <div className="details-content">
          <ChipGroup
            label={t("followup.delayedSymptoms")}
            options={delayedSymptomOptions}
            labels={labels.delayedSymptomLabels}
            values={draft.delayed_symptoms}
            onToggle={toggleSymptom}
          />
          <SelectField
            label={t("followup.recovery")}
            value={draft.recovery_status}
            options={labels.recoveryLabels}
            onChange={(value) => setDraft((current) => ({ ...current, recovery_status: value as RecoveryStatus | undefined }))}
          />
          <label className="text-field">
            <span>{t("register.note")}</span>
            <textarea
              maxLength={1000}
              value={draft.note}
              onChange={(event) => setDraft((current) => ({ ...current, note: event.target.value }))}
              rows={4}
            />
          </label>
        </div>
      </details>

      <div className="sticky-actions">
        {attempted && !draft.worse_than_before && <p className="error-text">{t("followup.missingWorse")}</p>}
        {savedText && <p className="success-text">{savedText}</p>}
        <button className="primary-action" disabled={!editAllowed} onClick={submit} type="button">
          <Send aria-hidden="true" />
          <span>{editing ? t("edit.saveChanges") : t("followup.saveFollowup")}</span>
        </button>
        {editing && onCancelEdit && (
          <button className="secondary-action" onClick={onCancelEdit} type="button">
            {t("edit.cancelEdit")}
          </button>
        )}
      </div>
    </section>
  );
}

function Overview({
  entries,
  selectedEntry,
  onSelect,
  onClose,
  onRequestEdit,
  onRequestDelete
}: {
  entries: StoredEntry[];
  selectedEntry?: StoredEntry;
  onSelect: (id: string) => void;
  onClose: () => void;
  onRequestEdit: (entry: StoredEntry) => void;
  onRequestDelete: (entry: StoredEntry) => void;
}) {
  const { labels, t } = useI18n();
  const [period, setPeriod] = useState<TrendPeriod>(() => readPeriod());

  const followupsByParent = useMemo(() => groupFollowups(entries), [entries]);
  const orphanFollowups = useMemo(
    () => entries.filter((entry): entry is DelayedFollowup => entry.type === "delayed_followup" && !findParentId(entry)),
    [entries]
  );
  const reports = useMemo(() => entries.filter((entry): entry is Report => entry.type === "work_report"), [entries]);

  const setStoredPeriod = (next: TrendPeriod) => {
    setPeriod(next);
    localStorage.setItem(PERIOD_KEY, JSON.stringify(next));
  };

  return (
    <section className="overview-layout">
      <div className="overview-main">
        <PeriodControls period={period} onChange={setStoredPeriod} />
        <Trend entries={entries} period={period} />

        <div className="entry-list">
          {reports.length === 0 && orphanFollowups.length === 0 && <p className="empty-text">{t("overview.noReports")}</p>}
          {reports.map((entry) => (
            <ReportListItem
              key={entry.id}
              report={entry}
              followups={followupsByParent.get(entry.id) ?? []}
              onSelect={onSelect}
            />
          ))}
          {orphanFollowups.map((entry) => (
            <button className="entry-row" key={entry.id} onClick={() => onSelect(entry.id)} type="button">
              <div>
                <strong>{t("followup.orphan")}</strong>
                <span>{formatDateTime(entry)}</span>
              </div>
              <span className="followup-worse">{t("followup.worsePrefix")}: <strong>{labels.worseLabels[entry.worse_than_before]}</strong></span>
            </button>
          ))}
        </div>
      </div>

      {selectedEntry && <ReportModal entry={selectedEntry} onClose={onClose} onRequestEdit={onRequestEdit} onRequestDelete={onRequestDelete} />}
    </section>
  );
}

function ReportModal({
  entry,
  onClose,
  onRequestEdit,
  onRequestDelete
}: {
  entry: StoredEntry;
  onClose: () => void;
  onRequestEdit: (entry: StoredEntry) => void;
  onRequestDelete: (entry: StoredEntry) => void;
}) {
  const { t } = useI18n();
  useEffect(() => {
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") onClose();
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [onClose]);

  return (
    <div className="modal-overlay report-overlay" role="presentation" onMouseDown={onClose}>
      <section className="report-modal" role="dialog" aria-modal="true" onMouseDown={(event) => event.stopPropagation()}>
        <button className="modal-close" onClick={onClose} type="button" aria-label={t("overview.closeReport")}>
          <X aria-hidden="true" />
        </button>
        <EntryDetails entry={entry} onRequestEdit={onRequestEdit} onRequestDelete={onRequestDelete} />
      </section>
    </div>
  );
}

function ReportListItem({
  report,
  followups,
  onSelect
}: {
  report: Report;
  followups: DelayedFollowup[];
  onSelect: (id: string) => void;
}) {
  const { labels, t } = useI18n();
  const score = getReportScore(report);
  return (
    <div className="report-group">
      <button className="entry-row" onClick={() => onSelect(report.id)} type="button">
        <div>
          <strong>{report.workplace || t("overview.report")}</strong>
          <span>
            {formatDateTime(report)}
            {report.work_start_time || report.work_end_time ? ` - ${report.work_start_time || "?"}-${report.work_end_time || "?"}` : ""}
          </span>
        </div>
        <div className="metrics-line">
          <MetricTag code="A" value={report.work_ability} />
          <MetricTag code="E" value={report.energy_level} />
          <MetricTag code="K" value={report.mental_clarity} />
          <MetricTag code="S" value={report.symptom_burden} />
          <MetricTag code="B" value={report.effort_strain} />
          {score !== null && <MetricTag code="T" value={`${score}%`} />}
        </div>
      </button>
      {followups.length > 0 && (
        <div className="followup-list">
          {followups.map((followup) => (
            <button className="followup-row" key={followup.id} onClick={() => onSelect(followup.id)} type="button">
              <span>{t("overview.followup")} {formatDateTime(followup)}</span>
              <span className="followup-worse">{t("followup.worsePrefix")}: <strong>{labels.worseLabels[followup.worse_than_before]}</strong></span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function MetricTag({ code, value }: { code: keyof typeof categoryTooltipTexts; value: React.ReactNode }) {
  const { labels } = useI18n();
  return (
    <span title={`${labels.categoryTooltipTexts[code]}: ${value}`}>
      {code} {value}
    </span>
  );
}

function VerificationIndicator({ entry }: { entry: StoredEntry }) {
  const { t } = useI18n();
  const status = entry.integrity?.verification_status;
  if (!status || status === "valid") return null;
  return (
    <button
      className="verification-warning"
      type="button"
      title={verificationTooltip(status)}
      aria-label={t("verification.invalidLabel")}
      onClick={(event) => event.stopPropagation()}
    >
      !
    </button>
  );
}

type TrendPeriod = {
  preset: PresetRange;
  startDate: string;
  endDate: string;
};

function PeriodControls({ period, onChange }: { period: TrendPeriod; onChange: (period: TrendPeriod) => void }) {
  const { t } = useI18n();
  const datePlaceholder = t("overview.datePlaceholder");
  const applyPreset = (preset: PresetRange) => {
    const today = new Date();
    const endDate = toDateInput(today);
    if (preset === "all") {
      onChange({ preset, startDate: "", endDate: "" });
      return;
    }
    if (preset === "custom") {
      onChange({ ...period, preset });
      return;
    }
    const start = new Date(today);
    const days = { "7d": 7, "1m": 31, "3m": 92, "6m": 183, "1y": 365, "3y": 1095, "5y": 1825 }[preset];
    start.setDate(start.getDate() - days);
    onChange({ preset, startDate: toDateInput(start), endDate });
  };

  return (
    <section className="period-panel">
      <div className="period-presets">
        {[
          ["7d", t("overview.week")],
          ["1m", t("overview.month")],
          ["3m", t("overview.threeMonths")],
          ["6m", t("overview.sixMonths")],
          ["1y", t("overview.year")],
          ["3y", t("overview.threeYears")],
          ["5y", t("overview.fiveYears")],
          ["all", t("overview.all")]
        ].map(([preset, label]) => (
          <button
            className={period.preset === preset ? "chip selected" : "chip"}
            key={preset}
            onClick={() => applyPreset(preset as PresetRange)}
            type="button"
          >
            {label}
          </button>
        ))}
      </div>
      <div className="date-range">
        <label>
          <span>{t("overview.fromDate")}</span>
          <input
            type="date"
            value={period.startDate}
            placeholder={datePlaceholder}
            aria-label={`${t("overview.fromDate")} ${datePlaceholder}`}
            onChange={(event) => onChange({ ...period, preset: "custom", startDate: event.target.value })}
          />
        </label>
        <label>
          <span>{t("overview.toDate")}</span>
          <input
            type="date"
            value={period.endDate}
            placeholder={datePlaceholder}
            aria-label={`${t("overview.toDate")} ${datePlaceholder}`}
            onChange={(event) => onChange({ ...period, preset: "custom", endDate: event.target.value })}
          />
        </label>
      </div>
    </section>
  );
}

function Trend({ entries, period, showScoreBands = false }: { entries: StoredEntry[]; period: TrendPeriod; showScoreBands?: boolean }) {
  const { labels, language, t } = useI18n();
  const [visibility, setVisibility] = useState<Record<string, LineVisibility>>({
    total_score_percent: "dim",
    followup_response: "normal"
  });
  const [fullscreenOpen, setFullscreenOpen] = useState(false);
  const reports = useMemo(() => {
    return filterReportsByPeriod(entries, period);
  }, [entries, period]);
  const followupResponses = useMemo(
    () => showScoreBands ? filterFollowupResponsesByPeriod(buildFollowupResponsePoints(entries), period) : [],
    [entries, period, showScoreBands]
  );
  const workTimeBands = useMemo(() => filterWorkTimeBandsByPeriod(buildWorkTimeBands(entries), period), [entries, period]);
  const periodAssessment = showScoreBands ? calculatePeriodWorkAbilityAssessment(reports) : null;

  const metrics = trendMetrics;

  if (reports.length < 2) {
    return (
      <section className="trend-panel">
        <div className="trend-head">
          <div className="inline-head">
            <h2>{t("overview.trend")}</h2>
            <Tooltip text={labels.tooltipTexts.total_score_percent} label={t("overview.explainTrend")} />
          </div>
          {showScoreBands && (
            <button className="graph-zoom-button" onClick={() => setFullscreenOpen(true)} type="button" aria-label={t("graphModal.open")}>
              <Search aria-hidden="true" />
            </button>
          )}
        </div>
        <p className="empty-text">{t("overview.minimumTwo")}</p>
        {showScoreBands && <PeriodAssessment assessment={periodAssessment} />}
        {showScoreBands && <FullscreenGraphModal entries={entries} initialPeriod={period} open={fullscreenOpen} onClose={() => setFullscreenOpen(false)} />}
      </section>
    );
  }

  const width = 760;
  const height = 260;
  const padding = { left: 36, right: 12, top: 18, bottom: 44 };
  const responseState = visibility[followupResponseMetric.key] ?? "normal";
  const visibleFollowupResponses = showScoreBands && responseState !== "hidden" ? followupResponses : [];
  const times = [
    ...reports.map(getEntryTime),
    ...visibleFollowupResponses.map((point) => getEntryTime(point.followup)),
    ...workTimeBands.flatMap((band) => [band.startAt, band.endAt])
  ];
  const [periodStart, periodEnd] = periodBounds(period);
  const minTime = graphDomainStart(times, periodStart);
  const maxTime = periodEnd ?? Math.max(...times);
  const range = Math.max(1, maxTime - minTime);
  const x = (time: number) => padding.left + ((time - minTime) / range) * (width - padding.left - padding.right);
  const y = (value: number) => padding.top + (1 - value / 100) * (height - padding.top - padding.bottom);
  const markers = createTimeMarkers(minTime, maxTime);
  const averages = metrics
    .map((metric) => ({
      ...metric,
      average: average(reports.map((report) => normalizeReportMetric(report, metric.key)).filter(isNumber))
    }))
    .filter((metric) => metric.average !== null);

  const cycleVisibility = (key: string) => {
    setVisibility((current) => {
      const next = current[key] === "dim" ? "hidden" : current[key] === "hidden" ? "normal" : "dim";
      return { ...current, [key]: next };
    });
  };

  return (
    <section className="trend-panel">
      <div className="trend-head">
        <div className="inline-head">
          <h2>{t("overview.trend")}</h2>
          <Tooltip text={t("overview.trendTooltip")} label={t("overview.explainTrend")} />
        </div>
        <div className="trend-actions">
          <p>{t("overview.average")}: {Math.round(averages.find((item) => item.key === "total_score_percent")?.average ?? 0)}%</p>
          {showScoreBands && (
            <button className="graph-zoom-button" onClick={() => setFullscreenOpen(true)} type="button" aria-label={t("graphModal.open")}>
              <Search aria-hidden="true" />
            </button>
          )}
        </div>
      </div>
      <svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={t("overview.chartLabel")}>
        {workTimeBands.map((band) => (
          <rect
            aria-label={t("graphModal.workTimeTooltip")}
            className="work-time-band"
            key={`work-time-${band.reportId}-${band.startAt}`}
            x={x(band.startAt)}
            y={padding.top}
            width={Math.max(1, x(band.endAt) - x(band.startAt))}
            height={height - padding.top - padding.bottom}
          />
        ))}
        {[0, 25, 50, 75, 100].map((line) => (
          <g key={line}>
            <line className="grid-line" x1={padding.left} x2={width - padding.right} y1={y(line)} y2={y(line)} />
            <text className="axis-label" x="0" y={y(line) + 4}>
              {line}
            </text>
          </g>
        ))}
        {markers.map((marker) => (
          <g key={`${marker.time}-${marker.label}`}>
            <line className="time-marker" x1={x(marker.time)} x2={x(marker.time)} y1={padding.top} y2={height - padding.bottom} />
            <text className="axis-label" x={x(marker.time)} y={height - 18} textAnchor="middle">
              {marker.label}
            </text>
          </g>
        ))}
        {metrics.flatMap((metric) => {
          const state = visibility[metric.key] ?? "normal";
          if (state === "hidden") return [];
          const points = reports
            .map((report) => {
              const value = normalizeReportMetric(report, metric.key);
              const time = getEntryTime(report);
              return value === null ? null : { x: x(time), y: y(value), time };
            })
            .filter(isPoint);
          return createLineSegments(points, padding.left).map((segment, index) => (
            <line
              key={`${metric.key}-${index}`}
              x1={segment.x1}
              x2={segment.x2}
              y1={segment.y1}
              y2={segment.y2}
              stroke={metric.color}
              strokeWidth={metric.key === "total_score_percent" ? "2.1" : "1.75"}
              strokeLinecap="round"
              opacity={state === "dim" ? 0.4 : 1}
              strokeDasharray={segment.dashed ? "8 8" : undefined}
            />
          ));
        })}
        {metrics.flatMap((metric) => {
          const state = visibility[metric.key] ?? "normal";
          if (state === "hidden") return [];
          return reports
            .map((report) => {
              const value = normalizeReportMetric(report, metric.key);
              const time = getEntryTime(report);
              return value === null ? null : { x: x(time), y: y(value), time };
            })
            .filter(isPoint)
            .map((point) => (
              <circle
                key={`${metric.key}-point-${point.time}`}
                cx={point.x}
                cy={point.y}
                r={metric.key === "total_score_percent" ? 4.5 : 4}
                fill={metric.color}
                opacity={state === "dim" ? 0.45 : 0.92}
              />
            ));
        })}
        {visibleFollowupResponses.map((point) => {
          const parentTime = getEntryTime(point.parent);
          const followupTime = getEntryTime(point.followup);
          const opacity = responseState === "dim" ? 0.42 : 0.9;
          return (
            <g key={`followup-response-${point.followup.id}`} opacity={opacity}>
              <line className="followup-link" x1={x(parentTime)} x2={x(followupTime)} y1={y(point.parentScore)} y2={y(point.effectScore)} />
              <line className="followup-effect-bar" x1={x(followupTime)} x2={x(followupTime)} y1={y(point.parentScore)} y2={y(point.effectScore)} />
              <circle className="followup-response-point" cx={x(followupTime)} cy={y(point.effectScore)} r="5" />
            </g>
          );
        })}
      </svg>
      <div className="legend interactive">
        {metrics.map((metric) => {
          const state = visibility[metric.key] ?? "normal";
          const metricAverage = averages.find((item) => item.key === metric.key)?.average ?? null;
          const band = metric.key === "total_score_percent" && showScoreBands ? getScoreBand(metricAverage) : null;
          return (
            <button
              className={`legend-chip ${state}`}
              key={metric.key}
              onClick={() => cycleVisibility(metric.key)}
              type="button"
              title={band ? `${metric.label}: ${Math.round(metricAverage ?? 0)}% - ${translateScoreBand(band, t)}. ${t("scoring.disclaimer")}` : metric.label}
            >
              <i style={{ background: metric.color }} /> {metric.short} {metric.label}
              {band && <span className="legend-band">- {translateScoreBand(band, t, "short")}</span>}
            </button>
          );
        })}
        {showScoreBands && (
          <button
            className={`legend-chip ${responseState}`}
            onClick={() => cycleVisibility(followupResponseMetric.key)}
            type="button"
            title={t("graphModal.followupResponseHelp")}
          >
            <i style={{ background: followupResponseMetric.color }} /> {followupResponseMetric.short} {t(followupResponseMetric.labelKey)}
          </button>
        )}
      </div>
      <p className="empty-text">{t("overview.dashedLine")}</p>
      <p className="empty-text">{t("graphModal.workTimeLegend")}</p>
      <div className="average-grid">
        {averages.map((metric) => (
          <span key={metric.key}>
            {metric.short}: {Math.round(metric.average ?? 0)}%
          </span>
        ))}
      </div>
      {showScoreBands && <PeriodAssessment assessment={periodAssessment} />}
      {showScoreBands && <FullscreenGraphModal entries={entries} initialPeriod={period} open={fullscreenOpen} onClose={() => setFullscreenOpen(false)} />}
    </section>
  );
}

function FullscreenGraphModal({
  entries,
  initialPeriod,
  open,
  onClose
}: {
  entries: StoredEntry[];
  initialPeriod: TrendPeriod;
  open: boolean;
  onClose: () => void;
}) {
  const { t } = useI18n();
  const [period, setPeriod] = useState(initialPeriod);
  const [visibility, setVisibility] = useState<Record<string, LineVisibility>>({ total_score_percent: "dim", followup_response: "normal" });
  const [inspect, setInspect] = useState<{ x: number; report: Report | null } | null>(null);
  const closeRef = useRef<HTMLButtonElement | null>(null);
  const svgRef = useRef<SVGSVGElement | null>(null);
  const modalRef = useRef<HTMLElement | null>(null);

  useEffect(() => {
    if (!open) return;
    setPeriod(initialPeriod);
    window.setTimeout(() => closeRef.current?.focus(), 0);
  }, [initialPeriod, open]);

  useEffect(() => {
    if (!open) return;
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") onClose();
      if (event.key !== "Tab" || !modalRef.current) return;
      const focusable = Array.from(
        modalRef.current.querySelectorAll<HTMLElement>("button, input, select, textarea, [href], [tabindex]:not([tabindex='-1'])")
      ).filter((element) => !element.hasAttribute("disabled"));
      if (focusable.length === 0) return;
      const first = focusable[0];
      const last = focusable[focusable.length - 1];
      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        last.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        first.focus();
      }
    };
    window.addEventListener("keydown", onKeyDown);
    return () => window.removeEventListener("keydown", onKeyDown);
  }, [onClose, open]);

  const reports = useMemo(() => filterReportsByPeriod(entries, period), [entries, period]);
  const followupsByParent = useMemo(() => groupFollowups(entries), [entries]);
  const followupResponses = useMemo(() => filterFollowupResponsesByPeriod(buildFollowupResponsePoints(entries), period), [entries, period]);
  const workTimeBands = useMemo(() => filterWorkTimeBandsByPeriod(buildWorkTimeBands(entries), period), [entries, period]);
  const width = 1120;
  const height = 520;
  const padding = { left: 44, right: 24, top: 26, bottom: 58 };
  const responseState = visibility[followupResponseMetric.key] ?? "normal";
  const visibleFollowupResponses = responseState !== "hidden" ? followupResponses : [];
  const times = [
    ...reports.map(getEntryTime),
    ...visibleFollowupResponses.map((point) => getEntryTime(point.followup)),
    ...workTimeBands.flatMap((band) => [band.startAt, band.endAt])
  ];
  const [periodStart, periodEnd] = periodBounds(period);
  const minTime = times.length ? graphDomainStart(times, periodStart) : graphDomainStart([Date.now()], periodStart);
  const maxTime = periodEnd ?? (times.length ? Math.max(...times) : minTime + 1);
  const range = Math.max(1, maxTime - minTime);
  const x = (time: number) => padding.left + ((time - minTime) / range) * (width - padding.left - padding.right);
  const y = (value: number) => padding.top + (1 - value / 100) * (height - padding.top - padding.bottom);
  const markers = createTimeMarkers(minTime, maxTime);

  const cycleVisibility = (key: string) => {
    setVisibility((current) => {
      const next = current[key] === "dim" ? "hidden" : current[key] === "hidden" ? "normal" : "dim";
      return { ...current, [key]: next };
    });
  };

  const inspectAt = (clientX: number) => {
    if (!svgRef.current || reports.length === 0) return;
    const rect = svgRef.current.getBoundingClientRect();
    const localX = ((clientX - rect.left) / rect.width) * width;
    const nearest = reports
      .map((report) => ({ report, distance: Math.abs(x(getEntryTime(report)) - localX) }))
      .sort((a, b) => a.distance - b.distance)[0];
    setInspect({
      x: Math.min(width - padding.right, Math.max(padding.left, localX)),
      report: nearest && nearest.distance <= 16 ? nearest.report : null
    });
  };

  if (!open) return null;

  return (
    <div className="modal-overlay graph-modal-overlay" role="presentation" onMouseDown={onClose}>
      <section ref={modalRef} className="graph-modal" role="dialog" aria-modal="true" aria-labelledby="graph-modal-title" onMouseDown={(event) => event.stopPropagation()}>
        <div className="graph-modal-head">
          <div>
            <p className="eyebrow">{t("evaluator.eyebrow")}</p>
            <h2 id="graph-modal-title">{t("graphModal.title")}</h2>
          </div>
          <button ref={closeRef} className="modal-close" onClick={onClose} type="button" aria-label={t("graphModal.close")}>
            <X aria-hidden="true" />
          </button>
        </div>

        <div className="graph-modal-controls">
          <PeriodControls period={period} onChange={setPeriod} />
          <section className="period-panel">
            <div className="field-head">
              <h2>{t("graphModal.visibleLines")}</h2>
              <p>{t("graphModal.visibleHelp")}</p>
            </div>
            <div className="legend interactive">
              {trendMetrics.map((metric) => {
                const state = visibility[metric.key] ?? "normal";
                return (
                  <button className={`legend-chip ${state}`} key={metric.key} onClick={() => cycleVisibility(metric.key)} type="button">
                    <i style={{ background: metric.color }} /> {metric.short} {metric.label}
                  </button>
                );
              })}
              <button className={`legend-chip ${responseState}`} onClick={() => cycleVisibility(followupResponseMetric.key)} type="button" title={t("graphModal.followupResponseHelp")}>
                <i style={{ background: followupResponseMetric.color }} /> {followupResponseMetric.short} {t(followupResponseMetric.labelKey)}
              </button>
            </div>
          </section>
        </div>

        <div className="graph-modal-body fullscreen-graph-body">
          <p className="empty-text fullscreen-graph-help">{t("graphModal.snapHelp")}</p>
          {reports.length < 1 ? (
            <p className="empty-text">{t("graphModal.noReports")}</p>
          ) : (
            <div className="graph-inspection-layout">
              <div className="graph-workspace">
                <div className="fullscreen-graph-chart-shell">
                  <svg
                    ref={svgRef}
                    className="fullscreen-chart"
                    viewBox={`0 0 ${width} ${height}`}
                    role="img"
                    aria-label={t("graphModal.chartLabel")}
                    onPointerLeave={() => setInspect(null)}
                    onPointerMove={(event) => inspectAt(event.clientX)}
                  >
                  {workTimeBands.map((band) => (
                    <rect
                      aria-label={t("graphModal.workTimeTooltip")}
                      className="work-time-band"
                      key={`fullscreen-work-time-${band.reportId}-${band.startAt}`}
                      x={x(band.startAt)}
                      y={padding.top}
                      width={Math.max(1, x(band.endAt) - x(band.startAt))}
                      height={height - padding.top - padding.bottom}
                    />
                  ))}
                  {[0, 25, 50, 75, 100].map((line) => (
                  <g key={line}>
                    <line className="grid-line" x1={padding.left} x2={width - padding.right} y1={y(line)} y2={y(line)} />
                    <text className="axis-label" x="0" y={y(line) + 4}>
                      {line}
                    </text>
                  </g>
                ))}
                {markers.map((marker) => (
                  <g key={`${marker.time}-${marker.label}`}>
                    <line className="time-marker" x1={x(marker.time)} x2={x(marker.time)} y1={padding.top} y2={height - padding.bottom} />
                    <text className="axis-label" x={x(marker.time)} y={height - 22} textAnchor="middle">
                      {marker.label}
                    </text>
                  </g>
                ))}
                  {trendMetrics.flatMap((metric) => {
                  const state = visibility[metric.key] ?? "normal";
                  if (state === "hidden") return [];
                  const points = reports
                    .map((report) => {
                      const value = normalizeReportMetric(report, metric.key);
                      const time = getEntryTime(report);
                      return value === null ? null : { x: x(time), y: y(value), time };
                    })
                    .filter(isPoint);
                  return createLineSegments(points, padding.left).map((segment, index) => (
                    <line
                      key={`${metric.key}-fullscreen-${index}`}
                      x1={segment.x1}
                      x2={segment.x2}
                      y1={segment.y1}
                      y2={segment.y2}
                      stroke={metric.color}
                      strokeWidth="1.75"
                      strokeLinecap="round"
                      opacity={state === "dim" ? 0.4 : 1}
                      strokeDasharray={segment.dashed ? "8 8" : undefined}
                    />
                  ));
                  })}
                  {visibleFollowupResponses.map((point) => {
                    const parentTime = getEntryTime(point.parent);
                    const followupTime = getEntryTime(point.followup);
                    const opacity = responseState === "dim" ? 0.42 : 0.9;
                    return (
                      <g key={`fullscreen-followup-response-${point.followup.id}`} opacity={opacity}>
                        <line className="followup-link" x1={x(parentTime)} x2={x(followupTime)} y1={y(point.parentScore)} y2={y(point.effectScore)} />
                        <line className="followup-effect-bar" x1={x(followupTime)} x2={x(followupTime)} y1={y(point.parentScore)} y2={y(point.effectScore)} />
                        <circle className="followup-response-point" cx={x(followupTime)} cy={y(point.effectScore)} r="6" />
                      </g>
                    );
                  })}
                  {trendMetrics.flatMap((metric) => {
                  const state = visibility[metric.key] ?? "normal";
                  if (state === "hidden") return [];
                  return reports
                    .map((report) => {
                      const value = normalizeReportMetric(report, metric.key);
                      const time = getEntryTime(report);
                      return value === null ? null : { x: x(time), y: y(value), time, reportId: report.id };
                    })
                    .filter((point): point is ChartPoint & { reportId: string } => Boolean(point))
                    .map((point) => (
                      <circle
                        key={`${metric.key}-fullscreen-point-${point.reportId}`}
                        className={inspect?.report?.id === point.reportId ? "chart-point active" : "chart-point"}
                        cx={point.x}
                        cy={point.y}
                        r={inspect?.report?.id === point.reportId ? 6.5 : 4.5}
                        fill={metric.color}
                        opacity={state === "dim" ? 0.45 : 0.95}
                      />
                    ));
                  })}
                  {inspect && <line className="inspect-line" x1={inspect.report ? x(getEntryTime(inspect.report)) : inspect.x} x2={inspect.report ? x(getEntryTime(inspect.report)) : inspect.x} y1={padding.top} y2={height - padding.bottom} />}
                  </svg>
                </div>
                <GraphSelectedPills report={inspect?.report ?? null} />
              </div>
              <GraphInspectTooltip
                followups={inspect?.report ? followupsByParent.get(inspect.report.id) ?? [] : []}
                report={inspect?.report ?? null}
                responsePoints={inspect?.report ? followupResponses.filter((point) => point.parent.id === inspect.report?.id) : []}
                workTimeBand={inspect?.report ? workTimeBands.find((band) => band.reportId === inspect.report?.id) ?? null : null}
                visibility={visibility}
              />
            </div>
          )}
        </div>
      </section>
    </div>
  );
}

function GraphSelectedPills({ report }: { report: Report | null }) {
  const { labels, t } = useI18n();
  if (!report) {
    return <div className="graph-selected-pills fullscreen-graph-below" aria-hidden="true" />;
  }
  return (
    <div className="graph-selected-pills fullscreen-graph-below">
      <PillGroup title={t("details.taskTypes")} values={report.task_types} labels={labels.taskTypeLabels} />
      <PillGroup title={t("details.accommodations")} values={report.helpful_accommodations} labels={labels.accommodationLabels} />
    </div>
  );
}

function PillGroup<T extends string>({ title, values, labels }: { title: string; values: T[]; labels: Record<T, string> }) {
  if (values.length === 0) return null;
  return (
    <div>
      <strong>{title}</strong>
      <span className="graph-status-pill-row">
        {values.map((value) => (
          <i className="graph-status-pill" key={value}>{labels[value]}</i>
        ))}
      </span>
    </div>
  );
}

function GraphInspectTooltip({
  followups,
  report,
  responsePoints,
  workTimeBand,
  visibility
}: {
  followups: DelayedFollowup[];
  report: Report | null;
  responsePoints: FollowupResponsePoint[];
  workTimeBand: WorkTimeBand | null;
  visibility: Record<string, LineVisibility>;
}) {
  const { labels, t } = useI18n();
  if (!report) {
    return (
      <aside className="graph-inspect-panel">
        <h3>{t("graphModal.noPoint")}</h3>
        <p>{t("graphModal.noPointHelp")}</p>
      </aside>
    );
  }

  return (
    <aside className="graph-inspect-panel">
      <h3>{t("graphModal.nearestReport")}</h3>
      <p>
        {formatDateTime(report)}
        <br />
        {report.workplace || t("common.unknownWorkplace")}
      </p>
      {workTimeBand && (
        <p>
          <strong>{t("graphModal.workTime")}:</strong> {formatTimeRange(workTimeBand.startAt, workTimeBand.endAt)}
        </p>
      )}
      <dl>
        {trendMetrics
          .filter((metric) => (visibility[metric.key] ?? "normal") !== "hidden")
          .map((metric) => {
            const value = normalizeReportMetric(report, metric.key);
            return (
              <div key={metric.key}>
                <dt>{metric.short}</dt>
                <dd>{value === null ? "-" : `${Math.round(value)}%`}</dd>
              </div>
            );
          })}
      </dl>
      <section className="graph-followups">
        <h4>{t("details.followups")}</h4>
        {followups.length === 0 && <p>{t("graphModal.noFollowups")}</p>}
        {followups
          .slice()
          .sort((a, b) => getEntryTime(a) - getEntryTime(b))
          .map((followup) => {
            const response = responsePoints.find((point) => point.followup.id === followup.id);
            return (
              <div key={followup.id}>
                <strong>{formatDateTime(followup)}</strong>
                <span>{t("details.worseValue", { value: labels.worseLabels[followup.worse_than_before] })}</span>
                {followup.recovery_status && <span>{t("details.recoveryValue", { value: labels.recoveryLabels[followup.recovery_status] })}</span>}
                {response && <span>{t("graphModal.followupDelay", { hours: response.offsetHours })}</span>}
                {response && <span>{t("graphModal.followupEffect", { score: response.effectScore, parent: response.parentScore })}</span>}
                {followup.note && <p>{followup.note}</p>}
              </div>
            );
          })}
      </section>
      {(visibility[followupResponseMetric.key] ?? "normal") !== "hidden" && responsePoints.length > 0 && (
        <p className="empty-text">{t("graphModal.followupResponseDisclaimer")}</p>
      )}
    </aside>
  );
}

function PeriodAssessment({ assessment }: { assessment: ReturnType<typeof calculatePeriodWorkAbilityAssessment> | null }) {
  const { t } = useI18n();
  return (
    <div className="period-assessment">
      <div>
        <strong>{t("scoring.periodLabel")}</strong>
        <span>
          {assessment?.score_percent === null || !assessment
            ? t("scoring.periodEmpty")
            : `${assessment.score_percent} % - ${translateScoreBand(assessment.score_band, t)}`}
        </span>
      </div>
      <Tooltip
        text={t("scoring.periodTooltip")}
        label={t("scoring.periodLabel")}
      />
    </div>
  );
}

function EntryDetails({
  entry,
  onRequestEdit,
  onRequestDelete
}: {
  entry: StoredEntry;
  onRequestEdit?: (entry: StoredEntry) => void;
  onRequestDelete?: (entry: StoredEntry) => void;
}) {
  const { labels, t } = useI18n();
  const related =
    entry.type === "delayed_followup" && findParentId(entry) ? listReports().find((item) => item.id === findParentId(entry)) : undefined;
  const editable = isEditableWithin24Hours(entry);

  return (
    <div className="details-view">
      <div className="detail-head">
        <div>
          <p className="eyebrow">{entry.type === "work_report" ? t("overview.report") : t("overview.followup")}</p>
          <h2>{entry.type === "work_report" ? entry.workplace || t("common.unknownWorkplace") : formatDateTime(entry)}</h2>
        </div>
      </div>

      {entry.type === "work_report" ? (
        <dl className="detail-grid">
          <Detail label={t("details.time")} value={formatDateTime(entry)} />
          <Detail label={t("register.workplace")} value={entry.workplace || t("common.unknownWorkplace")} />
          <Detail label={t("details.workTime")} value={`${entry.work_start_time || t("common.notSelected")} - ${entry.work_end_time || t("common.notSelected")}`} />
          <Detail label={t("details.totalScore")} value={`${getReportScore(entry) ?? calculateReportScore(entry) ?? t("common.notCalculated")}%`} />
          <Detail label={t("details.workAbility")} value={entry.work_ability} />
          <Detail label={t("details.energy")} value={entry.energy_level} />
          <Detail label={t("details.physicalEnergy")} value={entry.physical_energy} />
          <Detail label={t("details.mentalEnergy")} value={entry.mental_energy} />
          <Detail label={t("details.mentalClarity")} value={entry.mental_clarity} />
          <Detail label={t("details.symptoms")} value={entry.symptom_burden} />
          <Detail label={t("details.strain")} value={entry.effort_strain} />
          <Detail label={t("details.status")} value={labels.statusLabels[entry.status]} />
          <Detail label={t("details.productivity")} value={entry.perceived_productivity ?? t("common.notSelected")} />
          <Detail label={t("details.taskCompletion")} value={entry.task_completion ? labels.taskCompletionLabels[entry.task_completion] : t("common.notSelected")} />
          <Detail label={t("details.taskTypes")} value={labelsFor(entry.task_types, labels.taskTypeLabels, t)} />
          <Detail label={t("details.limitations")} value={labelsFor(entry.main_limitations, labels.limitationLabels, t)} />
          <Detail label={t("details.accommodations")} value={labelsFor(entry.helpful_accommodations, labels.accommodationLabels, t)} />
          <Detail label={t("details.comment")} value={entry.note ?? t("details.noComment")} />
        </dl>
      ) : (
        <dl className="detail-grid">
          <Detail label={t("details.worse")} value={labels.worseLabels[entry.worse_than_before]} />
          <Detail label={t("details.delayedSymptoms")} value={labelsFor(entry.delayed_symptoms, labels.delayedSymptomLabels, t)} />
          <Detail label={t("details.recovery")} value={entry.recovery_status ? labels.recoveryLabels[entry.recovery_status] : t("common.notSelected")} />
          <Detail label={t("details.connectedReport")} value={related ? formatDateTime(related) : t("common.notSelected")} />
          <Detail label={t("details.comment")} value={entry.note ?? t("details.noComment")} />
        </dl>
      )}
      {onRequestEdit && (
        <section className="edit-zone">
          {editable ? (
            <button className="secondary-action" onClick={() => onRequestEdit(entry)} type="button">
              <Save aria-hidden="true" />
              <span>{t("edit.edit")}</span>
            </button>
          ) : (
            <p className="empty-text">{t("edit.expired")}</p>
          )}
        </section>
      )}
      {onRequestDelete && (
        <section className="danger-zone">
          <h3>{t("danger.title")}</h3>
          <p>{t("danger.deleteEntryHelp")}</p>
          <button className="secondary-action danger" onClick={() => onRequestDelete(entry)} type="button">
            <Trash2 aria-hidden="true" />
            <span>{entry.type === "work_report" ? t("danger.deleteReport") : t("danger.deleteFollowup")}</span>
          </button>
        </section>
      )}
    </div>
  );
}

function Detail({ label, value }: { label: string; value: React.ReactNode }) {
  return (
    <div>
      <dt>{label}</dt>
      <dd>{value}</dd>
    </div>
  );
}

function PrivacyPanel({ demoMode, entries, onDeleted }: { demoMode: boolean; entries: StoredEntry[]; onDeleted: () => void }) {
  const { t } = useI18n();
  const [confirmDeleteAll, setConfirmDeleteAll] = useState(false);
  const [pendingImport, setPendingImport] = useState<{ text: string; summary: ImportSummary; fileName: string } | null>(null);
  const [importMessage, setImportMessage] = useState("");
  const [exportDialog, setExportDialog] = useState<"json" | "email" | null>(null);
  const [exportMessage, setExportMessage] = useState("");

  const clear = () => {
    deleteAllReports(demoMode);
    setConfirmDeleteAll(false);
    onDeleted();
  };

  const chooseImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    try {
      const text = await file.text();
      const summary = previewImportReports(text, demoMode);
      setPendingImport({ text, summary, fileName: file.name });
      setImportMessage("");
    } catch (error) {
      setPendingImport(null);
      setImportMessage(error instanceof Error ? error.message : "Importfilen kunne ikke leses.");
    } finally {
      event.target.value = "";
    }
  };

  const confirmImport = () => {
    if (!pendingImport) return;
    const summary = importReportsFromJson(pendingImport.text, demoMode);
    const flagged = summary.verification
      ? summary.verification.modified + summary.verification.missing + summary.verification.unsupported + summary.verification.error
      : 0;
    setPendingImport(null);
    setImportMessage(
      `Import fullført: ${summary.reports_added} nye rapporter, ${summary.followups_added} nye oppfølginger, ${summary.duplicates_skipped} duplikater hoppet over.${summary.demo_entries_detected ? " Demodata ble importert i demo-modus." : ""}${flagged > 0 ? " Noen rapporter kunne ikke verifiseres." : ""}`
    );
    onDeleted();
  };

  return (
    <section className="privacy-panel">
      <div className="notice warning">
        <ShieldAlert aria-hidden="true" />
        <span>{demoMode ? t("privacy.demoWarning") : t("privacy.warning")}</span>
      </div>

      <div className="action-grid">
        <button className="secondary-action" onClick={() => setExportDialog("json")} type="button" disabled={entries.length === 0}>
          <Download aria-hidden="true" />
          <span>{t("privacy.exportJson")}</span>
        </button>
        <button className="secondary-action" onClick={() => setExportDialog("email")} type="button" disabled={entries.length === 0}>
          <Send aria-hidden="true" />
          <span>{t("privacy.exportEmail")}</span>
        </button>
      </div>
      <div className="privacy-management-row">
        <label className="secondary-action file-inline">
          <Upload aria-hidden="true" />
          <span>{t("privacy.importBackup")}</span>
          <input accept="application/json,.json" onChange={chooseImport} type="file" />
        </label>
        <button className="secondary-action danger" onClick={() => setConfirmDeleteAll(true)} type="button" disabled={entries.length === 0}>
          <Trash2 aria-hidden="true" />
          <span>{t("privacy.deleteAll")}</span>
        </button>
      </div>

      <div className="notice">
        <FileJson aria-hidden="true" />
        <span>{t("privacy.importHelp")}</span>
      </div>

      {pendingImport && (
        <section className="import-review">
          <h2>{t("privacy.importFrom", { fileName: pendingImport.fileName })}</h2>
          <p>
            {t("privacy.importStats", {
              reports: pendingImport.summary.reports_added,
              followups: pendingImport.summary.followups_added,
              duplicates: pendingImport.summary.duplicates_skipped,
              conflicts: pendingImport.summary.conflicts_detected
            })}
          </p>
          {Boolean(pendingImport.summary.demo_entries_detected) && (
            <p className="error-text">{t("privacy.demoImportWarning", { count: pendingImport.summary.demo_entries_detected ?? 0 })}</p>
          )}
          <div className="action-grid">
            <button className="secondary-action" onClick={() => setPendingImport(null)} type="button">
              {t("privacy.cancelImport")}
            </button>
            <button className="primary-action" onClick={confirmImport} type="button">
              {t("privacy.importData")}
            </button>
          </div>
        </section>
      )}

      {importMessage && <p className="success-text">{importMessage}</p>}
      {exportMessage && <p className="success-text">{exportMessage}</p>}

      <p className="privacy-copy">
        {t("privacy.baseCopy")}
      </p>
      <div className="privacy-copy privacy-sections">
        <section>
          <h2>{t("privacy.storageTitle")}</h2>
          <p>{t("privacy.storageBody")}</p>
        </section>
        <section>
          <h2>{t("privacy.backupTitle")}</h2>
          <p>{t("privacy.backupBody")}</p>
        </section>
        <section>
          <h2>{t("privacy.sharingTitle")}</h2>
          <p>{t("privacy.sharingBody")}</p>
        </section>
        <section>
          <h2>{t("privacy.sensitiveTitle")}</h2>
          <p>{t("privacy.sensitiveBody")}</p>
        </section>
      </div>
      <p className="success-text">{t("privacy.exportIntegrity")}</p>
      {exportDialog && (
        <ExportModal
          demoMode={demoMode}
          mode={exportDialog}
          onClose={() => setExportDialog(null)}
          onMessage={setExportMessage}
        />
      )}
      <ConfirmDeleteModal open={confirmDeleteAll} itemName={t("privacy.deleteAllName")} onCancel={() => setConfirmDeleteAll(false)} onConfirm={clear} />
    </section>
  );
}

function ExportModal({
  demoMode,
  mode,
  onClose,
  onMessage
}: {
  demoMode: boolean;
  mode: "json" | "email";
  onClose: () => void;
  onMessage: (message: string) => void;
}) {
  const { t } = useI18n();
  const [range, setRange] = useState<ExportRange>({});
  const [recipient, setRecipient] = useState("");
  const [rangeOpen, setRangeOpen] = useState(false);
  const [serverConsent, setServerConsent] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");
  const [emailBackup, setEmailBackup] = useState<{ recipient: string; subject: string; body: string } | null>(null);
  const isEmail = mode === "email";

  const validate = () => {
    try {
      assertValidExportRange(range);
    } catch {
      setError(t("privacy.exportInvalidRange"));
      return false;
    }
    if (isEmail && !isProbablyEmail(recipient)) {
      setError(t("privacy.invalidEmail"));
      return false;
    }
    if (isEmail && !serverConsent) {
      setError(t("privacy.serverStorageConsentRequired"));
      return false;
    }
    setError("");
    return true;
  };

  const createFile = () => {
    const filename = buildExportFilename(demoMode, range);
    const json = exportReports(demoMode, range);
    return new File([json], filename, { type: "application/json" });
  };

  const submit = async () => {
    if (!validate()) return;
    setBusy(true);
    if (!isEmail) {
      const file = createFile();
      downloadFile(file);
      setBusy(false);
      onClose();
      return;
    }

    try {
      const share = await createSecureShare(exportReports(demoMode, range));
      const subject = t("privacy.emailSubject");
      const appUrl = window.location.origin;
      const body = t("privacy.secureEmailBody", { appUrl, shareUrl: share.shareUrl });
      const nextBackup = { recipient: recipient.trim(), subject, body };
      setEmailBackup(nextBackup);
      window.location.href = `mailto:${encodeURIComponent(nextBackup.recipient)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
      onMessage(t("privacy.emailLinkCreated"));
    } catch (error) {
      setError(error instanceof Error ? error.message : t("privacy.emailLinkFailed"));
    } finally {
      setBusy(false);
    }
  };

  const submitDisabled = busy || (isEmail && (!isProbablyEmail(recipient) || !serverConsent));

  return (
    <div className="modal-overlay report-overlay" role="presentation" onMouseDown={onClose}>
      <section className="export-modal" role="dialog" aria-modal="true" aria-labelledby="export-modal-title" onMouseDown={(event) => event.stopPropagation()}>
        <button className="modal-close" onClick={onClose} type="button" aria-label={t("common.close")}>
          <X aria-hidden="true" />
        </button>
        <div className="field-head">
          <h2 id="export-modal-title">{isEmail ? t("privacy.emailExportTitle") : t("privacy.exportRangeTitle")}</h2>
          <p>{isEmail ? t("privacy.emailClientNotice") : t("privacy.exportRangeIntro")}</p>
        </div>
        <p className="notice warning">{t("privacy.exportSensitiveNotice")}</p>
        {isEmail && (
          <label className="text-field">
            <span>{t("privacy.recipientEmail")}</span>
            <input
              inputMode="email"
              onChange={(event) => setRecipient(event.target.value)}
              placeholder={t("privacy.recipientPlaceholder")}
              type="email"
              value={recipient}
            />
          </label>
        )}
        {isEmail ? (
          <>
            <button className="secondary-action compact-toggle" onClick={() => setRangeOpen((open) => !open)} type="button">
              <FileClock aria-hidden="true" />
              <span>{t("privacy.limitPeriod")}</span>
            </button>
            {rangeOpen && <ExportRangeFields range={range} onChange={setRange} />}
            <p className="empty-text">{t("privacy.allDataSelected")}</p>
            <label className="consent-check">
              <input checked={serverConsent} onChange={(event) => setServerConsent(event.target.checked)} type="checkbox" />
              <span>{t("privacy.serverStorageConsent")}</span>
            </label>
          </>
        ) : (
          <>
            <ExportRangeFields range={range} onChange={setRange} />
            <p className="empty-text">{t("privacy.allDataDefault")}</p>
          </>
        )}
        {error && <p className="error-text">{error}</p>}
        <div className="action-grid">
          <button className="secondary-action" onClick={onClose} type="button">
            {t("common.cancel")}
          </button>
          <button className="primary-action" disabled={submitDisabled} onClick={submit} type="button">
            {isEmail ? t("privacy.createEmail") : t("privacy.exportJson")}
          </button>
        </div>
        {emailBackup && (
          <section className="copy-backup">
            <h3>{t("privacy.copyPasteBackupTitle")}</h3>
            <p>{t("privacy.copyPasteBackupIntro")}</p>
            <label className="text-field">
              <span>{t("privacy.recipientEmail")}</span>
              <input readOnly value={emailBackup.recipient} />
            </label>
            <label className="text-field">
              <span>{t("privacy.emailSubjectLabel")}</span>
              <input readOnly value={emailBackup.subject} />
            </label>
            <label className="text-field">
              <span>{t("privacy.emailBodyLabel")}</span>
              <textarea readOnly rows={10} value={emailBackup.body} />
            </label>
          </section>
        )}
      </section>
    </div>
  );
}

function ExportRangeFields({ range, onChange }: { range: ExportRange; onChange: (range: ExportRange) => void }) {
  const { t } = useI18n();
  const datePlaceholder = t("overview.datePlaceholder");
  return (
    <div className="date-range export-date-range">
      <label>
        <span>{t("privacy.fromDate")}</span>
        <input
          aria-label={`${t("privacy.fromDate")} ${datePlaceholder}`}
          onChange={(event) => onChange({ ...range, fromDate: event.target.value || undefined })}
          placeholder={datePlaceholder}
          type="date"
          value={range.fromDate ?? ""}
        />
      </label>
      <label>
        <span>{t("privacy.toDate")}</span>
        <input
          aria-label={`${t("privacy.toDate")} ${datePlaceholder}`}
          onChange={(event) => onChange({ ...range, toDate: event.target.value || undefined })}
          placeholder={datePlaceholder}
          type="date"
          value={range.toDate ?? ""}
        />
      </label>
    </div>
  );
}

function downloadFile(file: File) {
  const url = URL.createObjectURL(file);
  const link = document.createElement("a");
  link.href = url;
  link.download = file.name;
  link.click();
  URL.revokeObjectURL(url);
}

function isProbablyEmail(value: string) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
}

function SecureSharePage() {
  const { t } = useI18n();
  const request = useMemo(() => parseShareRequestFromLocation(window.location), []);
  const [status, setStatus] = useState<"idle" | "loading" | "done" | "error">("idle");
  const [message, setMessage] = useState("");

  const importShare = async () => {
    if (!request.exportId) {
      setStatus("error");
      setMessage(t("share.notFound"));
      return;
    }
    if (!request.key) {
      setStatus("error");
      setMessage(t("share.missingKey"));
      return;
    }
    setStatus("loading");
    setMessage("");
    try {
      const shared = await fetchSecureShare(request.exportId);
      if (shared.status !== "available") {
        if (shared.status === "deleted") throw new Error(t("share.alreadyDeleted"));
        if (shared.status === "expired") throw new Error(t("share.expired"));
        throw new Error(t("share.notFound"));
      }
      const payload = await decryptSecureSharePayload(shared, request.key);
      const result = importExportPayload(JSON.stringify(payload), "", undefined, false);
      await confirmSecureShareImport(request.exportId);
      setStatus("done");
      setMessage(
        t("share.imported", {
          reports: result.added,
          skipped: result.skipped
        })
      );
    } catch (error) {
      setStatus("error");
      setMessage(error instanceof Error ? error.message : t("share.decryptFailed"));
    }
  };

  const initialError = !request.key ? t("share.missingKey") : !request.exportId ? t("share.notFound") : "";

  return (
    <div className="app-shell share-shell">
      <header className="topbar">
        <div>
          <p className="eyebrow">{t("app.eyebrow")}</p>
          <h1>{t("share.title")}</h1>
        </div>
        <a className="text-link" href="/veileder">
          {t("app.evaluator")}
        </a>
      </header>
      <main>
        <section className="share-card">
          <div className="notice">
            <ShieldAlert aria-hidden="true" />
            <span>{t("share.intro")}</span>
          </div>
          <p>{t("share.activeImportNotice")}</p>
          {initialError && <p className="error-text">{initialError}</p>}
          {message && <p className={status === "done" ? "success-text" : "error-text"}>{message}</p>}
          <div className="action-grid">
            <button className="primary-action" disabled={Boolean(initialError) || status === "loading" || status === "done"} onClick={importShare} type="button">
              <Download aria-hidden="true" />
              <span>{status === "loading" ? t("share.fetching") : t("share.fetchAndImport")}</span>
            </button>
            <a className="secondary-action" href="/veileder">
              <UserRound aria-hidden="true" />
              <span>{t("share.openAdvisor")}</span>
            </a>
          </div>
          {status === "error" && <p className="empty-text">{t("share.requestNewShare")}</p>}
        </section>
      </main>
      <AppFooter demoMode={false} onDemoModeChange={() => undefined} showDemoToggle={false} />
    </div>
  );
}

function EvaluatorApp() {
  const { labels, t } = useI18n();
  const [demoMode, setDemoMode] = useState(() => isDemoModeEnabled());
  const [profiles, setProfiles] = useState<EvaluatorProfile[]>(() => listEvaluatorProfiles(isDemoModeEnabled()));
  const [selectedId, setSelectedId] = useState<string | null>(() => listEvaluatorProfiles(isDemoModeEnabled())[0]?.id ?? null);
  const [displayName, setDisplayName] = useState("");
  const [targetId, setTargetId] = useState("");
  const [message, setMessage] = useState("");
  const [personNote, setPersonNote] = useState("");
  const [selectedEntryId, setSelectedEntryId] = useState<string | null>(null);
  const [search, setSearch] = useState("");
  const [sortDir, setSortDir] = useState<"newest" | "oldest">("newest");
  const [deletePersonRequest, setDeletePersonRequest] = useState<EvaluatorProfile | null>(null);
  const selected = profiles.find((profile) => profile.id === selectedId);
  const selectedEntry = selectedEntryId ? selected?.reports.find((entry) => entry.id === selectedEntryId) : undefined;
  const period = readPeriod();

  useEffect(() => {
    if (selected) setPersonNote(getPersonNote(selected)?.text ?? "");
  }, [selected?.id]);

  useEffect(() => {
    const next = listEvaluatorProfiles(demoMode);
    setProfiles(next);
    setSelectedId((current) => (current && next.some((profile) => profile.id === current) ? current : next[0]?.id ?? null));
    setSelectedEntryId(null);
  }, [demoMode]);

  const refresh = (nextSelectedId?: string) => {
    const next = listEvaluatorProfiles(demoMode);
    setProfiles(next);
    setSelectedId((current) => nextSelectedId ?? (current && next.some((profile) => profile.id === current) ? current : next[0]?.id ?? null));
  };

  const changeDemoMode = (enabled: boolean) => {
    setDemoModeEnabled(enabled);
    if (enabled) {
      ensureDemoData();
      ensureDemoEvaluatorProfiles();
    }
    setDemoMode(enabled);
    setMessage(enabled ? t("demo.notice") : "");
  };

  const importFile = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;

    try {
      const result = importExportPayload(await file.text(), displayName, targetId || undefined, demoMode);
      const flagged = result.verification.modified + result.verification.missing + result.verification.unsupported + result.verification.error;
      const actionText =
        result.target_resolution === "matched_existing_person"
          ? "Importen ble knyttet til eksisterende bruker."
          : "Opprettet ny bruker.";
      setMessage(
        `${actionText} ${result.added} nye rapporter, ${result.skipped} duplikater hoppet over.${result.demo_entries_detected > 0 ? ` ADVARSEL: Importen inneholder ${result.demo_entries_detected} demodata-oppføringer.` : ""}${flagged > 0 ? " Noen rapporter kunne ikke verifiseres." : ""}`
      );
      setDisplayName("");
      setTargetId("");
      refresh(result.profile.id);
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Import feilet.");
    } finally {
      event.target.value = "";
    }
  };

  const importBackup = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    try {
      const text = await file.text();
      const demoEntries = countEvaluatorBackupDemoEntries(text);
      importEvaluatorBackup(text, demoMode);
      setMessage(`${t("evaluator.importedBackup")}${demoEntries > 0 ? ` ADVARSEL: Backupen inneholder ${demoEntries} demodata-oppføringer.` : ""}`);
      refresh();
    } catch (error) {
      setMessage(error instanceof Error ? error.message : "Backup kunne ikke importeres.");
    } finally {
      event.target.value = "";
    }
  };

  const downloadBackup = () => {
    const blob = new Blob([exportEvaluatorBackup(demoMode)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = `${demoMode ? "arbeidspuls-demo" : "arbeidspuls"}-veilederdata-${new Date().toISOString().slice(0, 10)}.json`;
    link.click();
    URL.revokeObjectURL(url);
  };

  const saveName = () => {
    if (!selected) return;
    renameEvaluatorProfile(selected.id, selected.display_name, demoMode);
    refresh(selected.id);
  };

  const savePersonNote = () => {
    if (!selected) return;
    upsertEvaluatorNote(selected.id, personNote, { note_type: "person" }, demoMode);
    refresh(selected.id);
  };

  const deleteSelectedPerson = () => {
    if (!deletePersonRequest) return;
    deleteEvaluatorProfile(deletePersonRequest.id, demoMode);
    const remaining = listEvaluatorProfiles(demoMode);
    setProfiles(remaining);
    setSelectedId(remaining[0]?.id ?? null);
    setSelectedEntryId(null);
    setDeletePersonRequest(null);
  };

  const reports = useMemo(() => {
    if (!selected) return [];
    const lowered = search.trim().toLowerCase();
    return selected.reports
      .filter((entry): entry is Report => entry.type === "work_report")
      .filter((report) => {
        if (!lowered) return true;
        return [report.workplace, report.note, report.report_date, labels.statusLabels[report.status]]
          .filter(Boolean)
          .join(" ")
          .toLowerCase()
          .includes(lowered);
      })
      .sort((a, b) => (sortDir === "newest" ? getEntryTime(b) - getEntryTime(a) : getEntryTime(a) - getEntryTime(b)));
  }, [labels.statusLabels, search, selected, sortDir]);

  const followupsByParent = useMemo(() => (selected ? groupFollowups(selected.reports) : new Map<string, DelayedFollowup[]>()), [selected]);

  return (
    <div className={demoMode ? "app-shell evaluator-shell demo-active" : "app-shell evaluator-shell"}>
      <header className="topbar">
        <div>
          <p className="eyebrow">{t("evaluator.eyebrow")}</p>
          <h1>{t("evaluator.title")}</h1>
        </div>
        <a className="text-link" href="/">
          {t("evaluator.backToApp")}
        </a>
      </header>

      {demoMode && (
        <div className="notice warning">
          <ShieldAlert aria-hidden="true" />
          <span>{t("demo.notice")}</span>
        </div>
      )}

      <main className="evaluator-layout">
        <section className="evaluator-sidebar">
          <div className="field-card">
            <div className="field-head">
              <h2>{t("evaluator.importJson")}</h2>
              <p>{t("evaluator.importHelp")}</p>
            </div>
            <label className="text-field">
              <span>{t("evaluator.displayName")}</span>
              <input value={displayName} onChange={(event) => setDisplayName(event.target.value)} placeholder={t("evaluator.optionalImportName")} />
            </label>
            <label className="select-field">
              <span>{t("evaluator.connectExisting")}</span>
              <select value={targetId} onChange={(event) => setTargetId(event.target.value)}>
                <option value="">{t("evaluator.autoMatch")}</option>
                {profiles.map((profile) => (
                  <option key={profile.id} value={profile.id}>
                    {profile.display_name}
                  </option>
                ))}
              </select>
            </label>
            <label className="file-button">
              <Upload aria-hidden="true" />
              <span>{t("evaluator.chooseJson")}</span>
              <input accept="application/json,.json" onChange={importFile} type="file" />
            </label>
            <div className="evaluator-backup-actions">
              <button className="secondary-action" onClick={downloadBackup} type="button" disabled={profiles.length === 0}>
                <Download aria-hidden="true" />
                <span>{t("evaluator.exportBackup")}</span>
              </button>
              <label className="secondary-action file-inline">
                <Upload aria-hidden="true" />
                <span>{t("evaluator.importBackup")}</span>
                <input accept="application/json,.json" onChange={importBackup} type="file" />
              </label>
            </div>
            {message && <p className="success-text">{message}</p>}
          </div>

          <div className="profile-list">
            {profiles.map((profile) => (
              <button
                className={selectedId === profile.id ? "profile-row active" : "profile-row"}
                key={profile.id}
                onClick={() => {
                  setSelectedId(profile.id);
                  setSelectedEntryId(null);
                }}
                type="button"
              >
                <UserRound aria-hidden="true" />
                <span>{profile.display_name}</span>
                <small>{t("evaluator.reportCount", { count: profile.reports.length })}</small>
              </button>
            ))}
          </div>
        </section>

        <section className="evaluator-main">
          {!selected && (
            <div className="notice">
              <FileJson aria-hidden="true" />
              <span>{t("evaluator.importStart")}</span>
            </div>
          )}
          {selected && (
            <div className="evaluator-main-grid">
              <div className="evaluator-left-workspace">
                {selected.contains_demo_data && (
                  <div className="notice warning">
                    <ShieldAlert aria-hidden="true" />
                    <span>{t("evaluator.demoDataWarning")}</span>
                  </div>
                )}
                <section className="field-card evaluator-name-card">
                  <label className="text-field">
                    <span>{t("evaluator.displayName")}</span>
                    <input
                      value={selected.display_name}
                      onChange={(event) => {
                        const value = event.target.value;
                        setProfiles((current) =>
                          current.map((profile) => (profile.id === selected.id ? { ...profile, display_name: value } : profile))
                        );
                      }}
                    />
                  </label>
                  <div className="evaluator-name-actions">
                    <button className="secondary-action" onClick={saveName} type="button">
                      <Save aria-hidden="true" />
                      <span>{t("evaluator.saveName")}</span>
                    </button>
                    <button
                      className="secondary-action danger"
                      onClick={() => setDeletePersonRequest(selected)}
                      title={t("evaluator.deletePersonTitle")}
                      type="button"
                    >
                      <Trash2 aria-hidden="true" />
                      <span>{t("evaluator.deletePerson")}</span>
                    </button>
                  </div>
                </section>

                <Trend entries={selected.reports} period={period} showScoreBands />

                <section className="field-card">
                  <div className="field-head">
                    <h2>{t("evaluator.personNote")}</h2>
                    <p>{t("evaluator.personNoteHelp")}</p>
                  </div>
                  <textarea
                    value={personNote}
                    onChange={(event) => setPersonNote(event.target.value)}
                    rows={4}
                  />
                  <button className="primary-action" onClick={savePersonNote} type="button">
                    <Save aria-hidden="true" />
                    <span>{t("evaluator.saveNote")}</span>
                  </button>
                  {getPersonNote(selected) && <p className="empty-text">{t("evaluator.changedAt", { time: formatIsoDateTime(getPersonNote(selected)!.updated_at) })}</p>}
                </section>

                <div className="review-tools">
                  <label className="search-field">
                    <Search aria-hidden="true" />
                    <input value={search} onChange={(event) => setSearch(event.target.value)} placeholder={t("evaluator.searchReports")} />
                  </label>
                  <select value={sortDir} onChange={(event) => setSortDir(event.target.value as "newest" | "oldest")}>
                    <option value="newest">{t("evaluator.newest")}</option>
                    <option value="oldest">{t("evaluator.oldest")}</option>
                  </select>
                </div>

                <div className="entry-list">
                  {reports.map((report) => (
                    <ReviewReportCard
                      followups={followupsByParent.get(report.id) ?? []}
                      key={report.id}
                      notes={selected.evaluator_notes}
                      onSelect={setSelectedEntryId}
                      report={report}
                      selectedId={selectedEntryId}
                    />
                  ))}
                </div>
              </div>
              <aside className="detail-pane evaluator-detail-pane">
                {selectedEntry ? (
                  <ReviewDetailPane
                    entry={selectedEntry}
                    followups={selectedEntry.type === "work_report" ? followupsByParent.get(selectedEntry.id) ?? [] : []}
                    notes={selected.evaluator_notes}
                    profileId={selected.id}
                    demoMode={demoMode}
                    onSaved={() => refresh(selected.id)}
                    onClose={() => setSelectedEntryId(null)}
                  />
                ) : (
                  <div className="detail-empty-state">
                    <h2>{t("evaluator.noSelectedTitle")}</h2>
                    <p>{t("evaluator.noSelectedBody")}</p>
                  </div>
                )}
              </aside>
            </div>
          )}
        </section>
      </main>
      <AppFooter demoMode={demoMode} onDemoModeChange={changeDemoMode} />
      <ConfirmDeleteModal
        open={Boolean(deletePersonRequest)}
        itemName={
          deletePersonRequest
            ? `${deletePersonRequest.display_name}, alle importerte rapporter, oppfølginger og veiledernotater knyttet til denne brukeren`
            : ""
        }
        onCancel={() => setDeletePersonRequest(null)}
        onConfirm={deleteSelectedPerson}
      />
    </div>
  );
}

function countEvaluatorBackupDemoEntries(jsonText: string) {
  try {
    const parsed = JSON.parse(jsonText) as { data_mode?: string; profiles?: EvaluatorProfile[] };
    if (!Array.isArray(parsed.profiles)) return 0;
    const count = parsed.profiles.reduce(
      (total, profile) => total + (profile.contains_demo_data ? 1 : 0) + profile.reports.filter((entry) => entry.demo || entry.id.startsWith("demo-arbeidspuls")).length,
      0
    );
    return parsed.data_mode === "demo" && count === 0 ? 1 : count;
  } catch {
    return 0;
  }
}

function ReviewReportCard({
  report,
  followups,
  notes,
  selectedId,
  onSelect
}: {
  report: Report;
  followups: DelayedFollowup[];
  notes: EvaluatorProfile["evaluator_notes"];
  selectedId: string | null;
  onSelect: (id: string) => void;
}) {
  const { labels, t } = useI18n();
  const reportNote = getReportNote(notes, report.id);
  const hasFollowupNote = followups.some((followup) => getFollowupNote(notes, followup.id));
  const selected = selectedId === report.id || followups.some((followup) => followup.id === selectedId);
  const score = getReportScore(report) ?? calculateReportScore(report);
  const band = getScoreBand(score);
  const scoreItems: ScorePillProps[] = [
    {
      code: "T",
      value: score === null ? null : `${score}%`,
      score,
      title: score === null ? `${t("details.totalScore")}: ${t("common.notCalculated")}` : `${t("details.totalScore")}: ${score}%${band ? ` - ${translateScoreBand(band, t)}` : ""}`
    },
    {
      code: "A",
      value: report.work_ability ?? null,
      score: normalizeReportMetric(report, "work_ability"),
      title: `${labels.categoryTooltipTexts.A}: ${report.work_ability ?? t("common.notProvided")}`
    },
    {
      code: "E",
      value: report.energy_level ?? null,
      score: normalizeReportMetric(report, "energy_level"),
      title: `${labels.categoryTooltipTexts.E}: ${report.energy_level ?? t("common.notProvided")}`
    },
    {
      code: "K",
      value: report.mental_clarity ?? null,
      score: normalizeReportMetric(report, "mental_clarity"),
      title: `${labels.categoryTooltipTexts.K}: ${report.mental_clarity ?? t("common.notProvided")}`
    },
    {
      code: "S",
      value: report.symptom_burden ?? null,
      score: normalizeReportMetric(report, "symptom_burden"),
      title: `${labels.categoryTooltipTexts.S}: ${report.symptom_burden ?? t("common.notProvided")} / 5`
    },
    {
      code: "B",
      value: report.effort_strain ?? null,
      score: normalizeReportMetric(report, "effort_strain"),
      title: `${labels.categoryTooltipTexts.B}: ${report.effort_strain ?? t("common.notProvided")} / 5`
    }
  ];
  return (
    <div className={selected ? "report-group selected-review" : "report-group"}>
      <button className="entry-row review-card-main" onClick={() => onSelect(report.id)} type="button">
        <div className="report-primary-info">
          <strong>{report.workplace || t("common.unknownWorkplace")}</strong>
          <span>{formatDateTime(report)}</span>
        </div>
        <div className="report-score-area">
          <div className="report-score-grid" aria-label={t("evaluator.scoreValues")}>
            {scoreItems.map((item) => (
              <ScorePill key={item.code} {...item} />
            ))}
          </div>
          <div className="report-meta-pill-row">
            {report.demo && <span className="demo-meta-pill" title={t("evaluator.demoDataWarning")}>{t("demo.label")}</span>}
            {report.integrity?.verification_status && report.integrity.verification_status !== "valid" && (
              <span className="verification-warning meta-pill" title={verificationTooltip(report.integrity.verification_status)}>
                ! <span>{t("evaluator.validationError")}</span>
              </span>
            )}
            {followups.length > 0 && <span title={t("evaluator.hasFollowup")}>{t("overview.followup")} {followups.length}</span>}
            {report.note && <span title={t("evaluator.hasComment")}>{t("evaluator.comment")}</span>}
            {reportNote && <span title={t("evaluator.hasEvaluatorNote")}>{t("evaluator.evaluatorNote")}</span>}
            {hasFollowupNote && <span title={t("evaluator.followupNoteBadge")}>{t("evaluator.followupNoteBadge")}</span>}
          </div>
        </div>
      </button>
      <button className="review-open" onClick={() => onSelect(report.id)} type="button">
        {t("evaluator.details")}
      </button>
      {followups.length > 0 && (
        <div className="followup-list">
          {followups.map((followup) => (
            <button className="followup-row" key={followup.id} onClick={() => onSelect(followup.id)} type="button">
              <span>{formatDateTime(followup)}</span>
              <strong>
                {t("followup.worsePrefix")}: {labels.worseLabels[followup.worse_than_before]} {getFollowupNote(notes, followup.id) ? `- ${t("evaluator.evaluatorNote")}` : ""}
              </strong>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

type ScorePillProps = {
  code: keyof typeof categoryTooltipTexts;
  value: React.ReactNode | null;
  score: number | null;
  title: string;
};

function ScorePill({ code, value, score, title }: ScorePillProps) {
  return (
    <span className={`report-score-pill ${scorePillClass(score)}`} title={title}>
      {code} {value ?? "-"}
    </span>
  );
}

function scorePillClass(score: number | null | undefined) {
  if (score === null || score === undefined || Number.isNaN(score)) return "score-missing";
  const value = Math.max(0, Math.min(100, score));
  if (value >= 90) return "score-high";
  if (value >= 70) return "score-good";
  if (value >= 50) return "score-moderate";
  if (value >= 30) return "score-low";
  return "score-very-low";
}

function ReviewDetailPane({
  entry,
  followups,
  notes,
  profileId,
  demoMode,
  onSaved,
  onClose
}: {
  entry: StoredEntry;
  followups: DelayedFollowup[];
  notes: EvaluatorProfile["evaluator_notes"];
  profileId: string;
  demoMode: boolean;
  onSaved: () => void;
  onClose: () => void;
}) {
  const { t } = useI18n();
  const currentNote = entry.type === "work_report" ? getReportNote(notes, entry.id) : getFollowupNote(notes, entry.id);
  const [noteText, setNoteText] = useState(currentNote?.text ?? "");
  const [confirmDeleteNote, setConfirmDeleteNote] = useState(false);

  useEffect(() => {
    setNoteText(currentNote?.text ?? "");
  }, [entry.id, currentNote?.id, currentNote?.updated_at]);

  const save = () => {
    if (entry.type === "work_report") {
      upsertEvaluatorNote(profileId, noteText, { note_type: "report", related_report_id: entry.id }, demoMode);
    } else {
      upsertEvaluatorNote(profileId, noteText, {
        note_type: "followup",
        related_followup_id: entry.id,
        parent_report_id: findParentId(entry)
      }, demoMode);
    }
    onSaved();
  };

  const remove = () => {
    if (entry.type === "work_report") {
      deleteEvaluatorNote(profileId, { note_type: "report", related_report_id: entry.id }, demoMode);
    } else {
      deleteEvaluatorNote(profileId, { note_type: "followup", related_followup_id: entry.id }, demoMode);
    }
    setNoteText("");
    onSaved();
  };

  return (
    <div className="review-detail">
      <div className="detail-head">
        <div>
          <p className="eyebrow">{entry.type === "work_report" ? t("evaluator.reportDetails") : t("evaluator.followupDetails")}</p>
          <h2>{entry.type === "work_report" ? entry.workplace || t("common.unknownWorkplace") : formatDateTime(entry)}</h2>
        </div>
        <button className="secondary-action slim" onClick={onClose} type="button" aria-label={t("evaluator.closeDetails")}>
          {t("evaluator.closeDetails")}
        </button>
      </div>

      {entry.type === "work_report" ? <ReportDetailSections report={entry} followups={followups} /> : <FollowupDetailSections followup={entry} />}

      <section className="field-card compact">
        <div className="field-head">
          <h2>{entry.type === "work_report" ? t("evaluator.reportNote") : t("evaluator.followupNote")}</h2>
          <p>{t("evaluator.noteHelp")}</p>
        </div>
        <textarea value={noteText} onChange={(event) => setNoteText(event.target.value)} rows={4} />
        <div className="action-grid">
          <button className="primary-action" onClick={save} type="button">
            {t("evaluator.saveNote")}
          </button>
          <button className="secondary-action danger" onClick={() => setConfirmDeleteNote(true)} type="button" disabled={!currentNote && !noteText}>
            {t("evaluator.deleteNote")}
          </button>
        </div>
      </section>
      <ConfirmDeleteModal
        open={confirmDeleteNote}
        itemName={entry.type === "work_report" ? t("evaluator.reportNote") : t("evaluator.followupNote")}
        onCancel={() => setConfirmDeleteNote(false)}
        onConfirm={() => {
          remove();
          setConfirmDeleteNote(false);
        }}
      />
      {entry.type === "work_report" ? <ReportTechnicalDetails report={entry} /> : <FollowupTechnicalDetails followup={entry} />}
    </div>
  );
}

function ReportDetailSections({ report, followups }: { report: Report; followups: DelayedFollowup[] }) {
  const { labels, language, t } = useI18n();
  const score = getReportScore(report) ?? calculateReportScore(report);
  const band = getScoreBand(score);
  const verificationStatus = report.integrity?.verification_status;
  const hasVerificationWarning = verificationStatus && verificationStatus !== "valid";
  return (
    <div className="detail-sections">
      <DetailSection title={t("details.summary")}>
        {report.demo && <Detail label={t("details.dataMode")} value={t("demo.label")} />}
        {hasVerificationWarning && (
          <Detail
            label={t("details.integrity")}
            value={<span className="verification-warning meta-pill" title={verificationTooltip(verificationStatus)}>! <span>{translateVerificationStatus(verificationStatus, t)}</span></span>}
          />
        )}
        <Detail label={t("details.time")} value={formatDateTime(report)} />
        <Detail label={t("register.workplace")} value={report.workplace || t("common.unknownWorkplace")} />
        <Detail label={t("details.workTime")} value={`${report.work_start_time || t("common.notProvided")} - ${report.work_end_time || t("common.notProvided")}`} />
        <Detail label={t("details.status")} value={labels.statusLabels[report.status]} />
        <Detail label={t("details.totalScore")} value={score === null ? t("common.notCalculated") : `${score}%`} />
        <Detail label={t("details.functionLevel")} value={translateScoreBand(band, t)} />
        <Detail label={t("details.description")} value={translateScoreBandDescription(band, language) ?? "Totalscore mangler eller kan ikke beregnes."} />
        <Detail label={t("details.important")} value={t("scoring.disclaimer")} />
      </DetailSection>
      <DetailSection title={t("details.coreValues")}>
        <Detail label={t("details.workAbility")} value={t("details.outOfFive", { value: report.work_ability })} />
        <Detail label={t("details.energy")} value={t("details.outOfFive", { value: report.energy_level })} />
        <Detail label={t("details.mentalClarity")} value={t("details.outOfFive", { value: report.mental_clarity })} />
        <Detail label={t("details.symptoms")} value={t("details.outOfFive", { value: report.symptom_burden })} />
        <Detail label={t("details.strain")} value={t("details.outOfFive", { value: report.effort_strain })} />
      </DetailSection>
      {hasReportDetails(report) ? (
        <DetailSection title={t("details.details")}>
          <Detail label={t("details.physicalEnergy")} value={t("details.outOfFive", { value: report.physical_energy })} />
          <Detail label={t("details.mentalEnergy")} value={t("details.outOfFive", { value: report.mental_energy })} />
          {report.perceived_productivity && <Detail label={t("details.productivity")} value={t("details.outOfFive", { value: report.perceived_productivity })} />}
          {report.task_completion && <Detail label={t("details.taskCompletion")} value={labels.taskCompletionLabels[report.task_completion]} />}
          {report.task_types.length > 0 && <Detail label={t("details.taskTypes")} value={labelsFor(report.task_types, labels.taskTypeLabels, t)} />}
          {report.main_limitations.length > 0 && <Detail label={t("details.limitations")} value={labelsFor(report.main_limitations, labels.limitationLabels, t)} />}
          {report.helpful_accommodations.length > 0 && <Detail label={t("details.accommodations")} value={labelsFor(report.helpful_accommodations, labels.accommodationLabels, t)} />}
        </DetailSection>
      ) : (
        <p className="empty-text">{t("details.noOptionalDetails")}</p>
      )}
      {report.note && (
        <DetailSection title={t("details.userComment")}>
          <Detail label={t("details.comment")} value={report.note} />
        </DetailSection>
      )}
      {followups.length > 0 && (
        <DetailSection title={t("details.followups")}>
          {followups.map((followup) => (
            <Detail
              key={followup.id}
              label={formatDateTime(followup)}
              value={`${t("details.worseValue", { value: labels.worseLabels[followup.worse_than_before] })}${followup.recovery_status ? ` - ${t("details.recoveryValue", { value: labels.recoveryLabels[followup.recovery_status] })}` : ""}`}
            />
          ))}
        </DetailSection>
      )}
    </div>
  );
}

function FollowupDetailSections({ followup }: { followup: DelayedFollowup }) {
  const { labels, t } = useI18n();
  const verificationStatus = followup.integrity?.verification_status;
  const hasVerificationWarning = verificationStatus && verificationStatus !== "valid";
  return (
    <div className="detail-sections">
      <DetailSection title={t("details.summary")}>
        {followup.demo && <Detail label={t("details.dataMode")} value={t("demo.label")} />}
        {hasVerificationWarning && (
          <Detail
            label={t("details.integrity")}
            value={<span className="verification-warning meta-pill" title={verificationTooltip(verificationStatus)}>! <span>{translateVerificationStatus(verificationStatus, t)}</span></span>}
          />
        )}
        <Detail label={t("details.time")} value={formatDateTime(followup)} />
        <Detail label={t("details.worse")} value={labels.worseLabels[followup.worse_than_before]} />
        {followup.recovery_status && <Detail label={t("details.recovery")} value={labels.recoveryLabels[followup.recovery_status]} />}
      </DetailSection>
      {(followup.delayed_symptoms.length > 0 || followup.note) && (
        <DetailSection title={t("details.details")}>
          {followup.delayed_symptoms.length > 0 && <Detail label={t("details.delayedSymptoms")} value={labelsFor(followup.delayed_symptoms, labels.delayedSymptomLabels, t)} />}
          {followup.note && <Detail label={t("details.userComment")} value={followup.note} />}
        </DetailSection>
      )}
    </div>
  );
}

function ReportTechnicalDetails({ report }: { report: Report }) {
  const { t } = useI18n();
  return (
    <>
      <TechnicalDetails>
        <p>{t("details.technicalDescription")}</p>
        <DetailSection title={t("details.technical")}>
          <Detail label={t("details.reportId")} value={report.id} />
          <Detail label="Type" value={report.type} />
          <Detail label={t("details.created")} value={formatIsoDateTime(report.created_at)} />
          {report.updated_at && <Detail label={t("details.updated")} value={formatIsoDateTime(report.updated_at)} />}
          <Detail label={t("details.reportDate")} value={report.report_date} />
          <Detail label={t("details.reportTime")} value={report.report_time} />
          <Detail label={t("details.integrityStatus")} value={translateVerificationStatus(report.integrity?.verification_status, t)} />
          <Detail label={t("details.explanation")} value={report.integrity?.verification_reason || verificationTooltip(report.integrity?.verification_status)} />
          <Detail label={t("details.algorithm")} value={report.integrity?.algorithm || t("common.notProvided")} />
          <Detail label="Canonicalization" value={report.integrity?.canonicalization || t("common.notProvided")} />
          <Detail label={t("details.signed")} value={report.integrity?.signed_at ? formatIsoDateTime(report.integrity.signed_at) : t("common.notProvided")} />
          <Detail label={t("details.payloadHash")} value={report.integrity?.payload_hash_sha256 || t("common.notProvided")} />
        </DetailSection>
      </TechnicalDetails>
      <RawDataDetails data={report} />
    </>
  );
}

function FollowupTechnicalDetails({ followup }: { followup: DelayedFollowup }) {
  const { t } = useI18n();
  return (
    <>
      <TechnicalDetails>
        <p>{t("details.technicalDescription")}</p>
        <DetailSection title={t("details.technical")}>
          <Detail label={t("details.followupId")} value={followup.id} />
          <Detail label={t("details.connectedReport")} value={findParentId(followup) || t("common.notSelected")} />
          <Detail label="Type" value={followup.type} />
          <Detail label={t("details.created")} value={formatIsoDateTime(followup.created_at)} />
          {followup.updated_at && <Detail label={t("details.updated")} value={formatIsoDateTime(followup.updated_at)} />}
          <Detail label={t("details.reportDate")} value={followup.report_date} />
          <Detail label={t("details.reportTime")} value={followup.report_time} />
          <Detail label={t("details.integrityStatus")} value={translateVerificationStatus(followup.integrity?.verification_status, t)} />
          <Detail label={t("details.explanation")} value={followup.integrity?.verification_reason || verificationTooltip(followup.integrity?.verification_status)} />
          <Detail label={t("details.algorithm")} value={followup.integrity?.algorithm || t("common.notProvided")} />
          <Detail label={t("details.payloadHash")} value={followup.integrity?.payload_hash_sha256 || t("common.notProvided")} />
        </DetailSection>
      </TechnicalDetails>
      <RawDataDetails data={followup} />
    </>
  );
}

function TechnicalDetails({ children }: { children: React.ReactNode }) {
  const { t } = useI18n();
  return (
    <details className="technical-details">
      <summary>{t("details.technical")}</summary>
      {children}
    </details>
  );
}

function RawDataDetails({ data }: { data: StoredEntry }) {
  const { t } = useI18n();
  const [copied, setCopied] = useState(false);
  const raw = JSON.stringify(data, null, 2);
  const copy = async () => {
    await navigator.clipboard?.writeText(raw);
    setCopied(true);
    window.setTimeout(() => setCopied(false), 1600);
  };
  return (
    <details className="raw-details">
      <summary>{t("details.rawData")}</summary>
      <p>{t("details.rawDescription")}</p>
      <button className="secondary-action slim" onClick={copy} type="button">
        {copied ? t("common.copied") : t("details.copyRaw")}
      </button>
      <pre>{raw}</pre>
    </details>
  );
}

function hasReportDetails(report: Report) {
  return Boolean(
    report.physical_energy ||
      report.mental_energy ||
      report.perceived_productivity ||
      report.task_completion ||
      report.task_types.length ||
      report.main_limitations.length ||
      report.helpful_accommodations.length
  );
}

function DetailSection({ title, children }: { title: string; children: React.ReactNode }) {
  return (
    <section className="detail-section">
      <h3>{title}</h3>
      <dl className="detail-grid">{children}</dl>
    </section>
  );
}

function labelsFor<T extends string>(values: T[] | undefined, labels: Record<T, string>, t?: ReturnType<typeof useI18n>["t"]) {
  return values?.length ? values.map((value) => labels[value]).join(", ") : t ? t("common.notSelected") : "Ikke valgt";
}

function getPersonNote(profile: EvaluatorProfile) {
  return profile.evaluator_notes.find((note) => (note.note_type ?? "person") === "person" && !note.related_report_id && !note.related_followup_id);
}

function getReportNote(notes: EvaluatorProfile["evaluator_notes"], reportId: string) {
  return notes.find((note) => (note.note_type === "report" || (!note.note_type && note.related_report_id)) && note.related_report_id === reportId);
}

function getFollowupNote(notes: EvaluatorProfile["evaluator_notes"], followupId: string) {
  return notes.find((note) => note.note_type === "followup" && note.related_followup_id === followupId);
}

function currentDateLocale() {
  return localStorage.getItem("arbeidspuls.language") === "en" ? "en-GB" : "nb-NO";
}

export function formatDateTime(entry: StoredEntry) {
  const parsed = new Date(`${entry.report_date}T${entry.report_time || "00:00:00"}`);
  if (Number.isNaN(parsed.getTime())) return `${entry.report_date} ${entry.report_time}`;
  const locale = currentDateLocale();
  const weekday = parsed.toLocaleDateString(locale, { weekday: "long" });
  const date = parsed.toLocaleDateString(locale, { day: "2-digit", month: "2-digit", year: "numeric" });
  return `${capitalize(weekday)} ${date} ${entry.report_time}`;
}

function formatShortDate(entry: StoredEntry) {
  const parsed = new Date(`${entry.report_date}T${entry.report_time || "00:00:00"}`);
  if (Number.isNaN(parsed.getTime())) return entry.report_date;
  const locale = currentDateLocale();
  const weekday = parsed.toLocaleDateString(locale, { weekday: "long" });
  const date = parsed.toLocaleDateString(locale, { day: "2-digit", month: "2-digit", year: "2-digit" });
  return `${capitalize(weekday)} ${date}`;
}

function formatReportPickerLabel(report: Report, labels: ReturnType<typeof useI18n>["labels"], t: ReturnType<typeof useI18n>["t"]) {
  const time = report.report_time.slice(0, 5);
  const workplace = report.workplace?.trim();
  const context = `${t("details.workAbility").toLowerCase()} ${report.work_ability}, ${labels.statusLabels[report.status].toLowerCase()}`;
  return workplace
    ? `${formatShortDate(report)} ${time} - ${workplace} (${context})`
    : `${formatShortDate(report)} ${time} - ${t("common.unknownWorkplace")} (${context})`;
}

function formatIsoDateTime(value: string) {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return value;
  const locale = currentDateLocale();
  const weekday = date.toLocaleDateString(locale, { weekday: "long" });
  const formatted = date.toLocaleDateString(locale, { day: "2-digit", month: "2-digit", year: "numeric" });
  const time = date.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit", second: "2-digit" });
  return `${capitalize(weekday)} ${formatted} ${time}`;
}

function capitalize(value: string) {
  return value.charAt(0).toUpperCase() + value.slice(1);
}

function findParentId(followup: DelayedFollowup) {
  return followup.parent_report_id ?? followup.related_report_id;
}

function groupFollowups(entries: StoredEntry[]) {
  const groups = new Map<string, DelayedFollowup[]>();
  entries
    .filter((entry): entry is DelayedFollowup => entry.type === "delayed_followup" && Boolean(findParentId(entry)))
    .forEach((followup) => {
      const parentId = findParentId(followup);
      if (!parentId) return;
      groups.set(parentId, [...(groups.get(parentId) ?? []), followup].sort((a, b) => getEntryTime(b) - getEntryTime(a)));
    });
  return groups;
}

function filterReportsByPeriod(entries: StoredEntry[], period: TrendPeriod) {
  const [start, end] = periodBounds(period);
  return entries
    .filter((entry): entry is Report => entry.type === "work_report")
    .filter((report) => {
      const time = getEntryTime(report);
      if (start && time < start) return false;
      if (end && time > end) return false;
      return true;
    })
    .sort((a, b) => getEntryTime(a) - getEntryTime(b));
}

function filterFollowupResponsesByPeriod(points: FollowupResponsePoint[], period: TrendPeriod) {
  const [start, end] = periodBounds(period);
  return points.filter((point) => {
    const time = getEntryTime(point.followup);
    if (start && time < start) return false;
    if (end && time > end) return false;
    return true;
  });
}

function readPeriod(): TrendPeriod {
  const fallback = { preset: "all" as PresetRange, startDate: "", endDate: "" };
  try {
    const parsed = JSON.parse(localStorage.getItem(PERIOD_KEY) ?? "");
    return parsed?.preset ? parsed : fallback;
  } catch {
    return fallback;
  }
}

function periodBounds(period: TrendPeriod): [number | null, number | null] {
  const start = period.startDate ? new Date(`${period.startDate}T00:00:00`).getTime() : null;
  const end = period.endDate ? new Date(`${period.endDate}T23:59:59`).getTime() : null;
  return [start, end];
}

export function graphDomainStart(times: number[], selectedStart: number | null = null) {
  const fallback = times.length ? Math.min(...times) : Date.now();
  return startOfDayTime(selectedStart ?? fallback);
}

function startOfDayTime(time: number) {
  const date = new Date(time);
  date.setHours(0, 0, 0, 0);
  return date.getTime();
}

function toDateInput(date: Date) {
  return [date.getFullYear(), String(date.getMonth() + 1).padStart(2, "0"), String(date.getDate()).padStart(2, "0")].join("-");
}

function createTimeMarkers(minTime: number, maxTime: number) {
  const span = maxTime - minTime;
  const candidates = [
    { unit: "time", step: 60 * 60 * 1000 },
    { unit: "day", step: 24 * 60 * 60 * 1000 },
    { unit: "week", step: 7 * 24 * 60 * 60 * 1000 },
    { unit: "month", step: 31 * 24 * 60 * 60 * 1000 },
    { unit: "year", step: 365 * 24 * 60 * 60 * 1000 }
  ];
  const selected = candidates.find((candidate) => span / candidate.step <= 8) ?? candidates[candidates.length - 1];
  const start = floorToInterval(minTime, selected.step);
  const markers: Array<{ time: number; label: string }> = [];
  for (let time = start; time <= maxTime; time += selected.step) {
    if (time >= minTime) markers.push({ time, label: formatMarker(time, selected.unit) });
  }
  if (!markers.some((marker) => marker.time === minTime)) {
    markers.unshift({ time: minTime, label: formatMarker(minTime, "day") });
  }
  return markers.slice(0, 10);
}

function floorToInterval(time: number, interval: number) {
  return Math.floor(time / interval) * interval;
}

function formatMarker(time: number, unit: string) {
  const date = new Date(time);
  const locale = currentDateLocale();
  if (unit === "time") return date.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
  if (unit === "day" || unit === "week") return date.toLocaleDateString(locale, { day: "2-digit", month: "2-digit" });
  if (unit === "month") return date.toLocaleDateString(locale, { month: "short", year: "2-digit" });
  return date.toLocaleDateString(locale, { year: "numeric" });
}

function formatTimeRange(startAt: number, endAt: number) {
  const locale = currentDateLocale();
  const options: Intl.DateTimeFormatOptions = { hour: "2-digit", minute: "2-digit" };
  return `${new Date(startAt).toLocaleTimeString(locale, options)}-${new Date(endAt).toLocaleTimeString(locale, options)}`;
}

function average(values: number[]) {
  if (values.length === 0) return null;
  return values.reduce((sum, value) => sum + value, 0) / values.length;
}

type ChartPoint = { x: number; y: number; time: number };
type WorkTimeBand = {
  reportId: string;
  startAt: number;
  endAt: number;
  workplace?: string;
};
type FollowupResponsePoint = {
  followup: DelayedFollowup;
  parent: Report;
  parentScore: number;
  effectScore: number;
  impact: number;
  offsetHours: number;
};

const followupResponseMetric = {
  key: "followup_response",
  labelKey: "graphModal.followupResponse",
  short: "O",
  color: "#b2642a"
};

function isPoint(value: ChartPoint | null): value is ChartPoint {
  return Boolean(value);
}

export function calculateFollowupEffectScore(
  parentScore: number,
  worseThanBefore: string,
  recoveryStatus?: string
) {
  const worseImpact: Record<string, number> = {
    bedre: 10,
    nei: 0,
    ikke_verre: 0,
    litt: -10,
    litt_verre: -10,
    moderat: -25,
    moderat_verre: -25,
    mye: -40,
    mye_verre: -40,
    svært_mye: -60,
    svaert_mye: -60,
    svært_mye_verre: -60,
    svaert_mye_verre: -60
  };
  const recoveryAdjustment: Record<string, number> = {
    fullt_restituert: 5,
    tilbake_til_baseline: 5,
    samme_dag: 5,
    delvis_restituert: 0,
    en_dag: 0,
    to_tre_dager: -5,
    ikke_restituert: -10,
    ikke_tilbake_til_baseline: -10,
    fire_pluss_dager: -10,
    forverret: -15
  };
  const impact = (worseImpact[worseThanBefore] ?? 0) + (recoveryStatus ? recoveryAdjustment[recoveryStatus] ?? 0 : 0);
  return Math.max(0, Math.min(100, parentScore + impact));
}

export function buildFollowupResponsePoints(entries: StoredEntry[]) {
  const reports = new Map(entries.filter((entry): entry is Report => entry.type === "work_report").map((report) => [report.id, report]));
  return entries
    .filter((entry): entry is DelayedFollowup => entry.type === "delayed_followup")
    .map((followup): FollowupResponsePoint | null => {
      const parentId = findParentId(followup);
      const parent = parentId ? reports.get(parentId) : undefined;
      if (!parent) return null;
      const parentScore = getReportScore(parent) ?? calculateReportScore(parent);
      if (parentScore === null) return null;
      const effectScore = calculateFollowupEffectScore(parentScore, followup.worse_than_before, followup.recovery_status);
      return {
        followup,
        parent,
        parentScore,
        effectScore,
        impact: effectScore - parentScore,
        offsetHours: Math.round(((getEntryTime(followup) - getEntryTime(parent)) / (60 * 60 * 1000)) * 10) / 10
      };
    })
    .filter((point): point is FollowupResponsePoint => Boolean(point))
    .sort((a, b) => getEntryTime(a.followup) - getEntryTime(b.followup));
}

export function buildWorkTimeBands(entries: StoredEntry[]) {
  return entries
    .filter((entry): entry is Report => entry.type === "work_report")
    .map((report): WorkTimeBand | null => {
      if (!report.work_start_time || !report.work_end_time) return null;
      const startAt = new Date(`${report.report_date}T${report.work_start_time}`).getTime();
      let endAt = new Date(`${report.report_date}T${report.work_end_time}`).getTime();
      if (!Number.isFinite(startAt) || !Number.isFinite(endAt)) return null;
      if (endAt <= startAt) endAt += 24 * 60 * 60 * 1000;
      return {
        reportId: report.id,
        startAt,
        endAt,
        workplace: report.workplace
      };
    })
    .filter((band): band is WorkTimeBand => Boolean(band))
    .sort((a, b) => a.startAt - b.startAt);
}

function filterWorkTimeBandsByPeriod(bands: WorkTimeBand[], period: TrendPeriod) {
  const [start, end] = periodBounds(period);
  return bands.filter((band) => {
    if (start && band.endAt < start) return false;
    if (end && band.startAt > end) return false;
    return true;
  });
}

export function createLineSegments(points: ChartPoint[], chartLeft: number) {
  const gapThresholdMs = 3 * 24 * 60 * 60 * 1000;
  if (points.length === 0) return [];
  const segments = [
    {
      x1: chartLeft,
      x2: points[0].x,
      y1: points[0].y,
      y2: points[0].y,
      dashed: true
    }
  ];
  for (let index = 1; index < points.length; index += 1) {
    const previous = points[index - 1];
    const current = points[index];
    segments.push({
      x1: previous.x,
      y1: previous.y,
      x2: current.x,
      y2: current.y,
      dashed: current.time - previous.time >= gapThresholdMs
    });
  }
  return segments;
}

function isNumber(value: number | null): value is number {
  return typeof value === "number" && Number.isFinite(value);
}

export default App;
