/* Helpers + state hooks */

const STORAGE_KEY = "rrr-progress-v1";
const META_KEY = "rrr-progress-meta-v1";
const SYNC_ENDPOINT_KEY = "rrr-sync-endpoint";
const SYNC_TOKEN_KEY = "rrr-sync-token";
const TODAY_OVERRIDE = null; // for testing
const RACE_ISO = "2026-09-20";
const TRAIN_START = "2026-04-28";

window.todayISO = function() {
  if (TODAY_OVERRIDE) return TODAY_OVERRIDE;
  const t = new Date();
  return t.getFullYear() + "-" +
    String(t.getMonth() + 1).padStart(2, "0") + "-" +
    String(t.getDate()).padStart(2, "0");
};

window.parseISO = function(iso) {
  const [y, m, d] = iso.split("-").map(Number);
  return new Date(y, m - 1, d);
};

window.daysBetween = function(a, b) {
  const A = window.parseISO(a), B = window.parseISO(b);
  return Math.round((B - A) / 86400000);
};

window.fmtMD = function(iso) {
  const [, m, d] = iso.split("-").map(Number);
  return m + "/" + d;
};

window.dowName = function(iso) {
  const D = ["週日", "週一", "週二", "週三", "週四", "週五", "週六"];
  return D[window.parseISO(iso).getDay()];
};

window.RACE_ISO = RACE_ISO;
window.TRAIN_START = TRAIN_START;

/* Persistence: progress = { "<iso>": { done: true, km: number } } */
window.loadProgress = function() {
  try {
    return JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
  } catch (e) {
    return {};
  }
};
window.saveProgress = function(p) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(p));
};
window.STORAGE_KEY = STORAGE_KEY;
window.META_KEY = META_KEY;
window.SYNC_ENDPOINT_KEY = SYNC_ENDPOINT_KEY;
window.SYNC_TOKEN_KEY = SYNC_TOKEN_KEY;

/* Cloud sync helpers — purely additive; localStorage stays the source of truth for the UI */
function getSyncConfig() {
  try {
    return {
      endpoint: localStorage.getItem(SYNC_ENDPOINT_KEY) || "",
      token: localStorage.getItem(SYNC_TOKEN_KEY) || ""
    };
  } catch (e) { return { endpoint: "", token: "" }; }
}
function getMeta() {
  try { return JSON.parse(localStorage.getItem(META_KEY) || "{}"); }
  catch (e) { return {}; }
}
function setMeta(m) {
  try { localStorage.setItem(META_KEY, JSON.stringify(m)); } catch (e) {}
}

window.getSyncConfig = getSyncConfig;
window.getSyncMeta = getMeta;

window.cloudFetch = async function() {
  const { endpoint, token } = getSyncConfig();
  if (!endpoint || !token) throw new Error("尚未設定雲端同步");
  const r = await fetch(endpoint.replace(/\/$/, "") + "/api/progress", {
    headers: { "Authorization": "Bearer " + token }
  });
  if (!r.ok) throw new Error("HTTP " + r.status);
  return r.json(); // { data, updatedAt } | { data: null }
};

window.cloudPush = async function(data, updatedAt) {
  const { endpoint, token } = getSyncConfig();
  if (!endpoint || !token) return;
  const r = await fetch(endpoint.replace(/\/$/, "") + "/api/progress", {
    method: "PUT",
    headers: {
      "Authorization": "Bearer " + token,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ data, updatedAt })
  });
  if (!r.ok) throw new Error("HTTP " + r.status);
  return r.json();
};

/* Custom hook: useProgress */
window.useProgress = function() {
  const [progress, setProgress] = React.useState(() => window.loadProgress());
  const [syncStatus, setSyncStatus] = React.useState("idle"); // idle | pulling | pushing | ok | error | off
  const pushTimer = React.useRef(null);

  // Pull on mount if sync is configured. Cloud wins if its updatedAt is newer.
  React.useEffect(() => {
    const { endpoint, token } = getSyncConfig();
    if (!endpoint || !token) { setSyncStatus("off"); return; }
    let cancelled = false;
    setSyncStatus("pulling");
    window.cloudFetch().then(res => {
      if (cancelled) return;
      const localMeta = getMeta();
      const remoteAt = res?.updatedAt || 0;
      const localAt = localMeta.updatedAt || 0;
      if (res?.data && remoteAt > localAt) {
        window.saveProgress(res.data);
        setMeta({ updatedAt: remoteAt });
        setProgress(res.data);
      } else if (res?.data == null && localAt > 0) {
        // Remote empty but we have local data — push it up
        window.cloudPush(window.loadProgress(), localAt).catch(() => {});
      }
      setSyncStatus("ok");
    }).catch(() => {
      if (!cancelled) setSyncStatus("error");
    });
    return () => { cancelled = true; };
  }, []);

  const schedulePush = React.useCallback((data) => {
    const { endpoint, token } = getSyncConfig();
    if (!endpoint || !token) return;
    if (pushTimer.current) clearTimeout(pushTimer.current);
    pushTimer.current = setTimeout(() => {
      const updatedAt = Date.now();
      setMeta({ updatedAt });
      setSyncStatus("pushing");
      window.cloudPush(data, updatedAt)
        .then(() => setSyncStatus("ok"))
        .catch(() => setSyncStatus("error"));
    }, 1500);
  }, []);

  const update = React.useCallback((iso, patch) => {
    setProgress(prev => {
      const next = { ...prev };
      const cur = next[iso] || {};
      const merged = { ...cur, ...patch };
      // remove if empty
      if (!merged.done && (merged.km == null || merged.km === "" || merged.km === 0)) {
        delete next[iso];
      } else {
        next[iso] = merged;
      }
      window.saveProgress(next);
      schedulePush(next);
      return next;
    });
  }, [schedulePush]);

  const resetAll = React.useCallback(() => {
    setProgress({});
    window.saveProgress({});
    schedulePush({});
  }, [schedulePush]);

  // Manual full sync — pulls cloud and overwrites local (useful from a fresh device)
  const syncNow = React.useCallback(async () => {
    setSyncStatus("pulling");
    try {
      const res = await window.cloudFetch();
      if (res?.data) {
        window.saveProgress(res.data);
        setMeta({ updatedAt: res.updatedAt || Date.now() });
        setProgress(res.data);
      }
      setSyncStatus("ok");
      return { ok: true };
    } catch (e) {
      setSyncStatus("error");
      return { ok: false, error: e.message };
    }
  }, []);

  return [progress, update, resetAll, syncStatus, syncNow];
};

/* Find current week given today */
window.findCurrentWeek = function(weeks, days) {
  const today = window.todayISO();
  // Find the week whose date range contains today
  for (const w of weeks) {
    const wd = days.filter(d => d.week === w.n);
    if (!wd.length) continue;
    const first = wd[0].iso, last = wd[wd.length - 1].iso;
    if (today >= first && today <= last) return w.n;
  }
  if (today < TRAIN_START) return 1;
  return weeks[weeks.length - 1].n;
};

/* Compute week stats: target km, actual km, completion % */
window.computeWeekStats = function(week, weekDays, progress) {
  const target = week.km;
  let actualKm = 0;
  let completedDays = 0;
  let totalDays = weekDays.length;
  for (const d of weekDays) {
    const p = progress[d.iso];
    if (p?.done) completedDays++;
    if (p?.km) actualKm += Number(p.km) || 0;
  }
  return { target, actualKm, completedDays, totalDays };
};

/* Ring progress component */
window.RingProgress = function({ pct, size = 36, stroke = 3, done }) {
  const r = (size - stroke) / 2;
  const c = 2 * Math.PI * r;
  const off = c * (1 - Math.min(1, Math.max(0, pct)));
  return (
    <div className={"week-prog-circle " + (done ? "done" : "")}>
      <svg width={size} height={size}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" strokeWidth={stroke} className="ring-bg" />
        <circle cx={size/2} cy={size/2} r={r} fill="none" strokeWidth={stroke}
                className="ring-fg" strokeDasharray={c} strokeDashoffset={off} strokeLinecap="round" />
      </svg>
      <span>{Math.round(pct * 100)}</span>
    </div>
  );
};
