// ics_parser.jsx — iCalendar (.ics) のシンプルなパーサ
// 目的: Google Calendar / iCloud / Outlook が吐く .ics を Echo の events 形式に変換。
// 対応: VEVENT 単発、終日イベント、タイムゾーン (DTSTART;TZID=)、ESCAPE文字、折り返し行。
// 非対応 (MVP): RRULE の展開 (→ seriesMaster として保存、TimelineViewでは skip)、VTIMEZONE 正式解釈 (Date parse に委譲)。

/**
 * .ics テキストをイベント配列にパース
 * @param {string} icsText
 * @param {object} opts — { sourceId, sourceName, sourceColor }
 * @returns {Array<Event>}
 */
function parseICS(icsText, opts = {}) {
  if (!icsText || typeof icsText !== 'string') return [];

  // 折り返し行をほどく (行頭がスペース/タブは前行の続き)
  const unfolded = icsText
    .replace(/\r\n/g, '\n')
    .replace(/\n[ \t]/g, '');

  const lines = unfolded.split('\n');
  const events = [];
  let calendarName = opts.sourceName || '';
  let inVEvent = false;
  let cur = null;

  for (const raw of lines) {
    const line = raw.trim();
    if (!line) continue;

    if (line.startsWith('X-WR-CALNAME:')) {
      calendarName = unescapeText(line.slice('X-WR-CALNAME:'.length));
      continue;
    }

    if (line === 'BEGIN:VEVENT') {
      inVEvent = true;
      cur = {};
      continue;
    }
    if (line === 'END:VEVENT') {
      if (cur) {
        const ev = normalizeEvent(cur, { ...opts, calendar: calendarName || opts.sourceName || '' });
        if (ev) events.push(ev);
      }
      cur = null;
      inVEvent = false;
      continue;
    }
    if (!inVEvent || !cur) continue;

    // key[;params]:value に分ける
    const colonIdx = line.indexOf(':');
    if (colonIdx < 0) continue;
    const keyRaw = line.slice(0, colonIdx);
    const value = line.slice(colonIdx + 1);
    const [key, ...paramParts] = keyRaw.split(';');
    const params = {};
    paramParts.forEach(p => {
      const eq = p.indexOf('=');
      if (eq > 0) params[p.slice(0, eq).toUpperCase()] = p.slice(eq + 1);
    });

    switch (key.toUpperCase()) {
      case 'UID':         cur.uid = value; break;
      case 'SUMMARY':     cur.title = unescapeText(value); break;
      case 'DESCRIPTION': cur.description = unescapeText(value); break;
      case 'LOCATION':    cur.location = unescapeText(value); break;
      case 'DTSTART':     cur.start = parseDT(value, params); break;
      case 'DTEND':       cur.end   = parseDT(value, params); break;
      case 'RRULE':       cur.rrule = value; break;
      case 'STATUS':      cur.status = value; break;
      case 'CATEGORIES':  cur.categories = value.split(',').map(s => s.trim()); break;
      default: break;
    }
  }

  return events;
}

function unescapeText(s) {
  if (!s) return '';
  return s.replace(/\\n/g, '\n').replace(/\\,/g, ',').replace(/\\;/g, ';').replace(/\\\\/g, '\\');
}

/**
 * DATE / DATE-TIME をタイムスタンプにパース
 * 形式: 20260324T140000Z (UTC) / 20260324T140000 (floating/local) / 20260324 (all-day)
 */
function parseDT(value, params) {
  if (!value) return null;
  const valueType = (params.VALUE || '').toUpperCase();
  const allDay = valueType === 'DATE' || /^\d{8}$/.test(value);
  if (allDay) {
    const y = +value.slice(0, 4), m = +value.slice(4, 6), d = +value.slice(6, 8);
    return { ts: new Date(y, m - 1, d, 0, 0, 0, 0).getTime(), allDay: true };
  }
  // DATE-TIME
  const m = value.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/);
  if (!m) return null;
  const [, Y, Mo, D, H, Mi, S, Z] = m;
  let ts;
  if (Z === 'Z') {
    ts = Date.UTC(+Y, +Mo - 1, +D, +H, +Mi, +S);
  } else if (params.TZID) {
    // TZID 指定あり: ブラウザの Intl API で近似。失敗したらローカル扱い。
    ts = parseInTZ(+Y, +Mo - 1, +D, +H, +Mi, +S, params.TZID);
  } else {
    // Floating: ローカル扱い
    ts = new Date(+Y, +Mo - 1, +D, +H, +Mi, +S).getTime();
  }
  return { ts, allDay: false, tzid: params.TZID || null };
}

/**
 * 指定タイムゾーンの壁時計を UTC タイムスタンプに変換 (近似)
 * 仕組み: まずローカル時刻として作ったTSを、そのTZ表現にフォーマット→差分オフセットを求めて補正。
 */
function parseInTZ(y, m, d, h, mi, s, tzid) {
  try {
    const asUTC = Date.UTC(y, m, d, h, mi, s);
    // tz の壁時計文字列を得る
    const fmt = new Intl.DateTimeFormat('en-US', {
      timeZone: tzid, year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false,
    });
    const parts = fmt.formatToParts(new Date(asUTC)).reduce((a, p) => (a[p.type] = p.value, a), {});
    const wallAsUTC = Date.UTC(
      +parts.year, +parts.month - 1, +parts.day,
      +parts.hour === 24 ? 0 : +parts.hour, +parts.minute, +parts.second
    );
    // 差分 = asUTC が本当に tz の壁時計 y-m-d-h-mi-s と一致するようにする
    const diff = asUTC - wallAsUTC;
    return asUTC + diff;
  } catch (e) {
    return new Date(y, m, d, h, mi, s).getTime();
  }
}

/**
 * タイトルからカテゴリを推定 (CALENDAR / CLASS / MEETING)
 */
function inferKind(title, categories) {
  const t = (title || '').toLowerCase();
  const cs = (categories || []).map(c => c.toLowerCase()).join(' ');
  const all = t + ' ' + cs;
  if (/class|lesson|授業|レッスン|英語|発音/.test(all)) return 'CLASS';
  if (/meeting|interview|1:1|standup|面接|ミーティング|打合せ|商談/.test(all)) return 'MEETING';
  if (/workout|gym|ジム|運動|ラン/.test(all)) return 'FITNESS';
  if (/flight|便|移動|travel/.test(all)) return 'TRAVEL';
  return 'CALENDAR';
}

function normalizeEvent(raw, ctx) {
  if (!raw.start || !raw.start.ts) return null;
  if (raw.status === 'CANCELLED') return null;
  const startTs = raw.start.ts;
  const endTs = raw.end ? raw.end.ts : startTs + 3600 * 1000;
  const allDay = !!raw.start.allDay;
  const title = raw.title || '(無題)';
  return {
    id: `ev:${ctx.sourceId || 'src'}:${raw.uid || (startTs + ':' + title)}`,
    kind: 'event',                   // note と区別
    ts: startTs,
    endTs: endTs,
    title: title,
    description: raw.description || '',
    location: raw.location || '',
    allDay: allDay,
    source: ctx.sourceId || 'default',
    sourceName: ctx.sourceName || ctx.calendar || 'Calendar',
    sourceColor: ctx.sourceColor || '#2E7D52',
    calendar: ctx.calendar || ctx.sourceName || '',
    category: inferKind(title, raw.categories),
    rrule: raw.rrule || null,        // 未展開 (MVP)
    recurring: !!raw.rrule,
  };
}

Object.assign(window, { parseICS, inferKind });
