// db.jsx — IndexedDB layer (薄いラッパ。idb-keyvalライク)
// Babelのasync/awaitトランスパイル時のスコープ問題を避けるため、
// ストア名は文字列リテラルで直接記述する。

function openDB() {
  return new Promise((resolve, reject) => {
    // v3: echo_events store 追加
    const req = indexedDB.open('echo.db.v1', 3);

    req.onupgradeneeded = (e) => {
      const db = e.target.result;

      if (!db.objectStoreNames.contains('notes')) {
        const os = db.createObjectStore('notes', { keyPath: 'id' });
        os.createIndex('ts', 'ts');
        os.createIndex('type', 'type');
      }

      if (!db.objectStoreNames.contains('meta')) {
        db.createObjectStore('meta', { keyPath: 'k' });
      }

      if (!db.objectStoreNames.contains('events')) {
        const es = db.createObjectStore('events', { keyPath: 'id' });
        es.createIndex('ts', 'ts');
        es.createIndex('source', 'source');
      }

      if (!db.objectStoreNames.contains('cal_sources')) {
        db.createObjectStore('cal_sources', { keyPath: 'id' });
      }

      // Echo履歴。
      // 新しい投下が過去メモとつながった瞬間を保存する。
      // これが「再会体験」のログになる。
      if (!db.objectStoreNames.contains('echo_events')) {
        const ee = db.createObjectStore('echo_events', { keyPath: 'id' });
        ee.createIndex('ts', 'ts');
        ee.createIndex('currentNoteId', 'currentNoteId');
        ee.createIndex('matchedNoteId', 'matchedNoteId');
        ee.createIndex('level', 'level');
        ee.createIndex('userAction', 'userAction');
      }
    };

    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
    req.onblocked = () => {
      console.warn('[echo] DB upgrade blocked — 他のタブでこのアプリが開かれていないか確認');
    };
  });
}

async function tx(store, mode, fn) {
  const db = await openDB();
  return new Promise((resolve, reject) => {
    const t = db.transaction(store, mode);
    const os = t.objectStore(store);
    const result = fn(os);
    t.oncomplete = () => resolve(result);
    t.onerror = () => reject(t.error);
    t.onabort = () => reject(t.error);
  });
}

const DB = {
  // ---- Notes ----
  async all() {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const req = db.transaction('notes', 'readonly').objectStore('notes').getAll();
      req.onsuccess = () => resolve(req.result || []);
      req.onerror = () => reject(req.error);
    });
  },

  async put(note) {
    await tx('notes', 'readwrite', (os) => os.put(note));
    return note;
  },

  async putMany(notes) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const t = db.transaction('notes', 'readwrite');
      const os = t.objectStore('notes');
      notes.forEach(n => os.put(n));
      t.oncomplete = () => resolve(notes);
      t.onerror = () => reject(t.error);
    });
  },

  async remove(id) {
    await tx('notes', 'readwrite', (os) => os.delete(id));
  },

  async clear() {
    await tx('notes', 'readwrite', (os) => os.clear());
  },

  // ---- Meta ----
  async setMeta(k, v) {
    await tx('meta', 'readwrite', (os) => os.put({ k, v }));
  },

  async getMeta(k) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const req = db.transaction('meta', 'readonly').objectStore('meta').get(k);
      req.onsuccess = () => resolve(req.result ? req.result.v : null);
      req.onerror = () => reject(req.error);
    });
  },

  // ---- Events (カレンダーイベント) ----
  async allEvents() {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const req = db.transaction('events', 'readonly').objectStore('events').getAll();
      req.onsuccess = () => resolve(req.result || []);
      req.onerror = () => reject(req.error);
    });
  },

  async putEvents(events) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const t = db.transaction('events', 'readwrite');
      const os = t.objectStore('events');
      events.forEach(e => os.put(e));
      t.oncomplete = () => resolve(events);
      t.onerror = () => reject(t.error);
    });
  },

  async removeEventsBySource(sourceId) {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const t = db.transaction('events', 'readwrite');
      const os = t.objectStore('events');
      const idx = os.index('source');
      const req = idx.openCursor(IDBKeyRange.only(sourceId));

      req.onsuccess = (e) => {
        const cur = e.target.result;
        if (cur) {
          cur.delete();
          cur.continue();
        }
      };

      t.oncomplete = () => resolve();
      t.onerror = () => reject(t.error);
    });
  },

  // ---- Sources (.ics 登録) ----
  async allSources() {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const req = db.transaction('cal_sources', 'readonly').objectStore('cal_sources').getAll();
      req.onsuccess = () => resolve(req.result || []);
      req.onerror = () => reject(req.error);
    });
  },

  async putSource(src) {
    await tx('cal_sources', 'readwrite', (os) => os.put(src));
    return src;
  },

  async removeSource(id) {
    await tx('cal_sources', 'readwrite', (os) => os.delete(id));
    await DB.removeEventsBySource(id);
  },

  // ---- Echo Events ----
  // Echo検出の履歴。
  // 「新しいメモ」と「過去メモ」がつながった瞬間を保存する。
  async allEchoEvents() {
    const db = await openDB();
    return new Promise((resolve, reject) => {
      const req = db.transaction('echo_events', 'readonly')
        .objectStore('echo_events')
        .getAll();

      req.onsuccess = () => {
        const rows = req.result || [];
        rows.sort((a, b) => b.ts - a.ts);
        resolve(rows);
      };
      req.onerror = () => reject(req.error);
    });
  },

  async putEchoEvent(ev) {
    await tx('echo_events', 'readwrite', (os) => os.put(ev));
    return ev;
  },

  async updateEchoEvent(id, patch) {
    const db = await openDB();

    return new Promise((resolve, reject) => {
      const t = db.transaction('echo_events', 'readwrite');
      const os = t.objectStore('echo_events');
      const getReq = os.get(id);

      getReq.onsuccess = () => {
        const current = getReq.result;
        if (!current) {
          resolve(null);
          return;
        }

        const next = {
          ...current,
          ...patch,
          updatedAt: Date.now(),
        };

        os.put(next);
        resolve(next);
      };

      getReq.onerror = () => reject(getReq.error);
      t.onerror = () => reject(t.error);
      t.onabort = () => reject(t.error);
    });
  },

  async removeEchoEvent(id) {
    await tx('echo_events', 'readwrite', (os) => os.delete(id));
  },

  async clearEchoEvents() {
    await tx('echo_events', 'readwrite', (os) => os.clear());
  },
};

function seedNotes() {
  const now = Date.now();
  const D = 24 * 3600 * 1000;

  const base = [
    {
      id: 'seed-1',
      ts: now - 21 * D,
      text: 'MetaのBD求人、ライセンシング経験が直接刺さる — multi-year deal, C-level stakeholder',
      type: 'idea',
      cal: 'Meta phone screen',
    },
    {
      id: 'seed-2',
      ts: now - 18 * D,
      text: '日曜に母親に電話',
      type: 'task',
      done: true,
    },
    {
      id: 'seed-3',
      ts: now - 18 * D,
      text: 'ピッチ軸は「メモが、自分が忘れていた大事なことを思い出させてくれる」かも',
      type: 'idea',
    },
    {
      id: 'seed-4',
      ts: now - 14 * D,
      text: 'AnimeJapanの原型ストーリー = 業界横断イベントをゼロから作った話。JDの市場開発に対応',
      type: 'memo',
      cal: 'Meta phone screen',
      promoted: 'idea',
    },
    {
      id: 'seed-5',
      ts: now - 12 * D,
      text: 'コーヒー豆買う',
      type: 'task',
      done: true,
    },
    {
      id: 'seed-6',
      ts: now - 10 * D,
      text: '成果報酬型提案書 v1 完成 — C社に送った',
      type: 'output',
    },
    {
      id: 'seed-7',
      ts: now - 8 * D,
      text: 'C社契約成立 ¥420k 入金確定',
      type: 'outcome',
      value: 420000,
    },
    {
      id: 'seed-8',
      ts: now - 7 * D,
      text: 'Q1ライセンシング、2分版・数字から入る（15年パートナーシップ）',
      type: 'task',
      cal: 'Meta phone screen',
      done: true,
    },
    {
      id: 'seed-9',
      ts: now - 5 * D,
      text: 'SVO崩壊、即興20問ドリル5月まで継続',
      type: 'task',
      cal: 'English class',
    },
    {
      id: 'seed-10',
      ts: now - 3 * D,
      text: '音楽ビジネスの権利ホルダーとの長期関係、信頼貯金じゃなくて透明性貯金な気がする',
      type: 'idea',
    },
    {
      id: 'seed-11',
      ts: now - 2 * D,
      text: '5月の帰国便予約',
      type: 'task',
    },
    {
      id: 'seed-12',
      ts: now - 1 * D,
      text: '今日の授業、SD先生の発音ドリル。リズムが先で音素が後という気づき',
      type: 'memo',
    },
  ];

  // すべてのseedに status を付与
  return base.map(n => ({
    ...n,
    status: (now - n.ts) < 7 * D ? 'fresh' : 'active',
  }));
}

async function ensureSeeded() {
  const seeded = await DB.getMeta('seeded');
  if (seeded) return;

  await DB.putMany(seedNotes());
  await DB.setMeta('seeded', true);
}

// ==================== Calendar seed ====================
// デモ用の .ics ソース2つ (JIC授業 + 仕事・個人) とイベントを注入
function seedCalendarSources() {
  return [
    {
      id: 'src-seed-class',
      name: 'JIC Premium',
      fileName: 'jic-schedule.ics',
      color: '#2E7D52',
      importedAt: Date.now() - 30 * 24 * 3600 * 1000,
      eventCount: 0,
      seed: true,
    },
    {
      id: 'src-seed-work',
      name: '仕事・面接',
      fileName: 'work.ics',
      color: '#5B6CFF',
      importedAt: Date.now() - 20 * 24 * 3600 * 1000,
      eventCount: 0,
      seed: true,
    },
  ];
}

function seedCalendarEvents() {
  const H = 3600 * 1000;
  const D = 24 * H;
  const now = new Date();
  const today7 = new Date(
    now.getFullYear(),
    now.getMonth(),
    now.getDate(),
    7,
    0,
    0,
    0
  ).getTime();

  const daysAgo = (n, h, m = 0) => {
    return today7 - n * D + (h - 7) * H + m * 60 * 1000;
  };

  const cls = 'src-seed-class';
  const wrk = 'src-seed-work';
  const GREEN = '#2E7D52';
  const INDIGO = '#5B6CFF';

  const mk = (id, source, color, ts, dur, title, category, location = '') => ({
    id: `ev:${source}:${id}`,
    kind: 'event',
    ts,
    endTs: ts + dur * H,
    title,
    description: '',
    location,
    allDay: false,
    source,
    sourceName: source === cls ? 'JIC Premium' : '仕事・面接',
    sourceColor: color,
    calendar: source === cls ? 'JIC Premium' : '仕事・面接',
    category,
    rrule: null,
    recurring: false,
  });

  return [
    mk('today-sd', cls, GREEN, daysAgo(0, 12, 30), 1, 'SD先生 発音レッスン', 'CLASS', 'Room 302'),
    mk('today-ted', cls, GREEN, daysAgo(0, 15, 0), 1, 'TED 面接練習', 'CLASS', 'Room 207'),
    mk('today-meta', wrk, INDIGO, daysAgo(0, 19, 0), 1, 'Meta リクルーター電話', 'MEETING', 'Zoom'),

    mk('y-bon', cls, GREEN, daysAgo(1, 9, 0), 1, 'Bon プレゼン指導', 'CLASS', 'Room 105'),
    mk('y-connie', cls, GREEN, daysAgo(1, 11, 0), 1, 'Connie イディオム', 'CLASS', 'Room 212'),
    mk('y-echo', cls, GREEN, daysAgo(1, 14, 0), 1, 'ECHO 文化トーク', 'CLASS', 'Room 301'),

    mk('d2-sd', cls, GREEN, daysAgo(2, 12, 30), 1, 'SD先生 スピーチ', 'CLASS', 'Room 302'),
    mk('d2-prep', wrk, INDIGO, daysAgo(2, 20, 0), 2, 'Meta面接 準備 (STAR書き換え)', 'MEETING', ''),

    mk('d3-ted', cls, GREEN, daysAgo(3, 15, 0), 1, 'TED フリートーク', 'CLASS', 'Room 207'),

    mk('d5-sd', cls, GREEN, daysAgo(5, 12, 30), 1, 'SD先生 ARE練習', 'CLASS'),
    mk('d5-meta', wrk, INDIGO, daysAgo(5, 10, 0), 1, 'Meta JD 読み込み', 'MEETING'),
  ];
}

async function ensureSeededCalendar() {
  if (typeof DB.getMeta !== 'function') return;

  const seeded = await DB.getMeta('cal_seeded');
  if (seeded) return;

  const sources = seedCalendarSources();
  const events = seedCalendarEvents();

  sources.forEach(s => {
    s.eventCount = events.filter(e => e.source === s.id).length;
  });

  for (const s of sources) {
    await DB.putSource(s);
  }

  await DB.putEvents(events);
  await DB.setMeta('cal_seeded', true);
}

async function resetDemo() {
  await DB.clear();

  // Echo履歴もリセット
  try {
    await DB.clearEchoEvents();
  } catch (e) {
    console.warn('[echo] clearEchoEvents failed:', e);
  }

  await DB.putMany(seedNotes());
  await DB.setMeta('seeded', true);

  const sources = await DB.allSources();
  for (const s of sources) {
    await DB.removeSource(s.id);
  }

  await DB.setMeta('cal_seeded', false);
  await ensureSeededCalendar();
}

Object.assign(window, {
  DB,
  seedNotes,
  ensureSeeded,
  resetDemo,
  seedCalendarSources,
  seedCalendarEvents,
  ensureSeededCalendar,
});
