// リッチエディタ（textarea[data-rich-editor]）の入力内容を localStorage に退避し、
// リロード・誤操作・タブ落ちで書きかけが失われないようにする「下書き」機構。
//
// ライフサイクル
//   1) 入力のたび（デバウンス）に現在値を保存。ただしベースライン（＝読み込み時の値）と
//      一致 or 空なら削除する＝「未保存の差分」だけを持つ。
//   2) エディタ生成時、ベースラインと異なる下書きがあれば復元し、小さな「復元しました」
//      チップを出す（破棄でベースラインへ戻せる）。
//   3) 通常フォーム送信時は下書きに submittedAt を刻む（まだ消さない）。次のページ
//      読み込みで判定：検証エラーが出ていれば残す（＝送信失敗→再表示）、無ければ削除。
//   4) AJAX 保存（インライン編集など）は成功コールバックで clearDraft() を呼ぶ。
//   5) 古い下書き（14日 / 上限件数超過）は読み込み時にまとめて掃除する。
//
// キーは textarea の data-draft-key を最優先。無ければ URL パス + フォーム action +
// name + 同種フィールド内の位置から自動導出する。data-no-draft で無効化できる。
//
// ※ 元 Relay/backlog-clone の汎用フォーム下書き（initFieldScopes / fancy-select）は
//   アプリ側の関心事なので本パッケージには含めない（エディタ本文の下書きのみ）。

const NS = 'rich-editor:draft:v1';
const MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000; // 14 日で失効
const MAX_ENTRIES = 100; // 名前空間あたりの最大保持件数（超過は古い順に破棄）
const SAVE_DEBOUNCE_MS = 350;

// localStorage はプライベートブラウズや設定で例外を投げることがある。使えるか一度だけ
// 判定し、駄目なら全 API を no-op にしてページを壊さない。
let _store;
function store() {
    if (_store !== undefined) return _store;
    try {
        const probe = NS + ':probe';
        window.localStorage.setItem(probe, '1');
        window.localStorage.removeItem(probe);
        _store = window.localStorage;
    } catch (e) {
        _store = null;
    }
    return _store;
}

// 同じブラウザを複数アカウントで使っても下書きが混ざらないよう、ユーザー ID で名前空間を分ける
// （body[data-uid]。未ログイン/未設定なら 'anon'）。
function uid() {
    return (document.body && document.body.dataset.uid) || 'anon';
}
function fullKey(key) { return `${NS}:${uid()}:${key}`; }

function getRecord(key) {
    const s = store(); if (!s) return null;
    try { return JSON.parse(s.getItem(fullKey(key)) || 'null'); } catch (e) { return null; }
}
function putRecord(key, rec) {
    const s = store(); if (!s) return;
    const raw = JSON.stringify(rec);
    try { s.setItem(fullKey(key), raw); }
    catch (e) { pruneAll(); try { s.setItem(fullKey(key), raw); } catch (e2) { /* quota: 諦める */ } }
}
function delKey(key) { const s = store(); if (s) { try { s.removeItem(fullKey(key)); } catch (e) {} } }

// 下書きの保存キー。明示 data-draft-key が最優先。無ければ自動導出（パス+action+name+位置）。
export function draftKeyFor(el) {
    if (!el || el.dataset.noDraft !== undefined) return null;
    if (el.dataset.draftKey) return el.dataset.draftKey;
    const form = el.closest('form');
    const action = form ? (form.getAttribute('action') || 'noaction') : 'noform';
    const name = el.getAttribute('name') || 'field';
    // 同じフォーム・同じ name を持つエディタの中での位置（同名フィールドが並ぶ場合の区別）。
    const scope = form || document;
    const sibs = Array.from(scope.querySelectorAll('textarea[data-rich-editor]'))
        .filter((t) => t.closest('form') === form && (t.getAttribute('name') || 'field') === name);
    const idx = Math.max(0, sibs.indexOf(el));
    return `${location.pathname}::${action}::${name}::${idx}`;
}

// 保存（デバウンス）。値が空 or ベースライン一致なら「未保存の差分なし」として削除。
const _timers = new WeakMap();
export function scheduleSave(el, value, baseline) {
    const key = draftKeyFor(el); if (!key) return;
    clearTimeout(_timers.get(el));
    _timers.set(el, setTimeout(() => saveNow(key, value, baseline), SAVE_DEBOUNCE_MS));
}
// 即時保存（blur 時など、デバウンス待ちを取りこぼさないため）。
export function flushSave(el, value, baseline) {
    const key = draftKeyFor(el); if (!key) return;
    clearTimeout(_timers.get(el));
    saveNow(key, value, baseline);
}
function saveNow(key, value, baseline) {
    const v = value == null ? '' : String(value);
    const base = baseline == null ? '' : String(baseline);
    if (v.trim() === '' || v === base) { delKey(key); return; }
    putRecord(key, { v, base, t: Date.now() });
}

// 復元候補を返す（無ければ null）。ベースライン一致・空は掃除してから null。
export function restoreDraft(el, baseline) {
    const key = draftKeyFor(el); if (!key) return null;
    const rec = getRecord(key);
    if (!rec || typeof rec.v !== 'string') return null;
    const base = baseline == null ? '' : String(baseline);
    if (rec.v.trim() === '' || rec.v === base) { delKey(key); return null; }
    return rec.v;
}

export function clearDraft(el) { const key = draftKeyFor(el); if (key) delKey(key); }
export function clearDraftByKey(key) { if (key) delKey(key); }

// 通常フォーム送信時、そのフォーム内エディタの下書きに submittedAt を刻む（削除は次の読込で判定）。
function stampSubmitted(el) {
    const key = draftKeyFor(el); if (!key) return;
    const rec = getRecord(key);
    if (rec) { rec.submittedAt = Date.now(); putRecord(key, rec); }
}

// 名前空間内の全レコードを走査するヘルパ。cb(key, rec) を呼ぶ。
function eachRecord(cb) {
    const s = store(); if (!s) return;
    const prefix = NS + ':';
    for (let i = 0; i < s.length; i++) {
        const sk = s.key(i);
        if (!sk || sk.indexOf(prefix) !== 0) continue;
        let rec; try { rec = JSON.parse(s.getItem(sk) || 'null'); } catch (e) { rec = null; }
        cb(sk, rec);
    }
}

// 期限切れ・壊れたレコードを掃除し、件数上限を超えたぶんを古い順に破棄。
function pruneAll() {
    const s = store(); if (!s) return;
    const now = Date.now();
    const alive = [];
    const dead = [];
    eachRecord((sk, rec) => {
        if (!rec || (rec.t && now - rec.t > MAX_AGE_MS)) dead.push(sk);
        else alive.push({ sk, t: rec.t || 0 });
    });
    dead.forEach((sk) => { try { s.removeItem(sk); } catch (e) {} });
    if (alive.length > MAX_ENTRIES) {
        alive.sort((a, b) => a.t - b.t);
        alive.slice(0, alive.length - MAX_ENTRIES).forEach((e) => { try { s.removeItem(e.sk); } catch (er) {} });
    }
}

// 読み込み時：送信済み（submittedAt 付き）下書きを成否で解決する。
//   ・ページに検証エラーがある（body[data-form-errors]）→ 送信失敗とみなし印だけ消して残す
//     （エディタが再表示されれば復元される）。
//   ・エラーが無い → 送信成功とみなし削除（作成フォームの下書きが残り続けるのを防ぐ）。
// 対象は現在ユーザーの名前空間のみ。
function reconcileSubmitted() {
    const s = store(); if (!s) return;
    const hasErrors = document.body && document.body.dataset.formErrors !== undefined;
    const myPrefix = `${NS}:${uid()}:`;
    const toDelete = [];
    eachRecord((sk, rec) => {
        if (!rec || sk.indexOf(myPrefix) !== 0 || !rec.submittedAt) return;
        if (hasErrors) { delete rec.submittedAt; try { s.setItem(sk, JSON.stringify(rec)); } catch (e) {} }
        else toDelete.push(sk);
    });
    toDelete.forEach((sk) => { try { s.removeItem(sk); } catch (e) {} });
}

// 「下書きを復元しました・破棄」チップを anchor の直前に差し込む。破棄で onDiscard を実行。
export function showRestoredChip(anchor, onDiscard) {
    if (!anchor || !anchor.parentNode) return null;
    const chip = document.createElement('div');
    chip.className = 'draft-restored';
    const label = document.createElement('span');
    label.className = 'draft-restored-tx';
    label.innerHTML = '<span class="draft-restored-dot"></span>下書きを復元しました';
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'draft-restored-x';
    btn.textContent = '破棄';
    btn.addEventListener('click', () => { try { onDiscard(); } catch (e) {} remove(); });
    chip.append(label, btn);
    anchor.parentNode.insertBefore(chip, anchor);
    let gone = false;
    const remove = () => { if (gone) return; gone = true; chip.remove(); };
    // 数秒でそっと消す（下書き自体は残す。破棄は明示操作のときだけ）。
    setTimeout(() => chip.classList.add('is-fading'), 5000);
    setTimeout(remove, 6200);
    return chip;
}

// 掃除＋送信済み判定＋送信フックを一度だけ仕込む。mountRichEditor より前に呼ぶこと。
let _inited = false;
export function initDrafts() {
    if (_inited) return; _inited = true;
    if (!store()) return;
    pruneAll();
    reconcileSubmitted();
    // 通常フォーム送信の捕捉（capture）。data-no-draft のフォームは対象外。
    // フォーム内エディタの下書きに submittedAt を刻む（削除は次の読込で判定）。
    document.addEventListener('submit', (e) => {
        const form = e.target;
        if (!form || !form.querySelectorAll || form.dataset.noDraft !== undefined) return;
        form.querySelectorAll('textarea[data-rich-editor]').forEach((t) => stampSubmitted(t));
    }, true);
}
