/*
 * Copyright (c) 2026 光安 達仁
 */

const state = {
  dataset: null,
  mode: "standard",
  metric: "perDayYen",
  baseVersionId: "",
  compareVersionId: "",
  standardSelected: [],
  revisionSelected: [],
  searches: {
    standard: "",
    revision: "",
    crossLeft: "",
    crossRight: "",
  },
  primaryFilters: {
    standard: { mdc: "", dpc6: "" },
    revision: { mdc: "", dpc6: "" },
    crossLeft: { mdc: "", dpc6: "" },
    crossRight: { mdc: "", dpc6: "" },
  },
  filters: {
    standard: [],
    revision: [],
    crossLeft: [],
    crossRight: [],
  },
  filterPanels: {
    standard: false,
    revision: false,
    crossLeft: false,
    crossRight: false,
  },
  cross: {
    leftVersionId: "",
    rightVersionId: "",
    leftCode: "",
    rightCode: "",
  },
  hoverDay: null,
  renderToken: 0,
  loadError: "",
};

const entryDetailCache = new Map();
const pendingChunkLoads = new Map();

const palette = ["#005f73", "#bb3e03", "#9b2226", "#0a9396", "#ee9b00", "#6c584c", "#3a86ff", "#7c6a0a"];

const metricLabels = {
  perDayYen: "1日金額",
  cumulativeYen: "累積金額",
  averageYen: "平均金額",
};

const conditionGroupLabels = {
  patient: "患者条件",
  surgery: "手術",
  procedure: "処置",
  diagnosis: "診断条件",
  clinical: "臨床条件",
  modifier: "修飾条件",
};

const filterConfig = {
  standard: {
    groups: document.querySelector("#standard-condition-groups"),
    active: document.querySelector("#standard-active-filters"),
    clear: document.querySelector("#clear-standard-filters"),
    toggle: document.querySelector("#toggle-standard-filters"),
    panel: document.querySelector("#standard-controls .filter-panel"),
  },
  revision: {
    groups: document.querySelector("#revision-condition-groups"),
    active: document.querySelector("#revision-active-filters"),
    clear: document.querySelector("#clear-revision-filters"),
    toggle: document.querySelector("#toggle-revision-filters"),
    panel: document.querySelector("#revision-controls .filter-panel"),
  },
  crossLeft: {
    groups: document.querySelector("#cross-left-condition-groups"),
    active: document.querySelector("#cross-left-active-filters"),
    clear: document.querySelector("#clear-cross-left-filters"),
    toggle: document.querySelector("#toggle-cross-left-filters"),
    panel: document.querySelector("#crossyear-controls #cross-left-condition-groups").closest(".filter-panel"),
  },
  crossRight: {
    groups: document.querySelector("#cross-right-condition-groups"),
    active: document.querySelector("#cross-right-active-filters"),
    clear: document.querySelector("#clear-cross-right-filters"),
    toggle: document.querySelector("#toggle-cross-right-filters"),
    panel: document.querySelector("#crossyear-controls #cross-right-condition-groups").closest(".filter-panel"),
  },
};

const els = {
  datasetStatus: document.querySelector("#dataset-status"),
  datasetMeta: document.querySelector("#dataset-meta"),
  datasetSourceLink: document.querySelector("#dataset-source-link"),
  chartSubtitle: document.querySelector("#chart-subtitle"),
  chartLegend: document.querySelector("#chart-legend"),
  hoverCard: document.querySelector("#hover-card"),
  chart: document.querySelector("#chart"),
  seriesSummary: document.querySelector("#series-summary"),
  baseVersionSelect: document.querySelector("#base-version-select"),
  revisionBaseSelect: document.querySelector("#revision-base-select"),
  revisionCompareSelect: document.querySelector("#revision-compare-select"),
  standardMdcSelect: document.querySelector("#standard-mdc-select"),
  standardDpc6Select: document.querySelector("#standard-dpc6-select"),
  revisionMdcSelect: document.querySelector("#revision-mdc-select"),
  revisionDpc6Select: document.querySelector("#revision-dpc6-select"),
  searchInput: document.querySelector("#search-input"),
  revisionSearchInput: document.querySelector("#revision-search-input"),
  selectedStandard: document.querySelector("#selected-standard"),
  selectedRevision: document.querySelector("#selected-revision"),
  searchResults: document.querySelector("#search-results"),
  revisionResults: document.querySelector("#revision-results"),
  standardControls: document.querySelector("#standard-controls"),
  revisionControls: document.querySelector("#revision-controls"),
  crossYearControls: document.querySelector("#crossyear-controls"),
  crossLeftVersion: document.querySelector("#cross-left-version"),
  crossRightVersion: document.querySelector("#cross-right-version"),
  crossLeftMdcSelect: document.querySelector("#cross-left-mdc-select"),
  crossLeftDpc6Select: document.querySelector("#cross-left-dpc6-select"),
  crossRightMdcSelect: document.querySelector("#cross-right-mdc-select"),
  crossRightDpc6Select: document.querySelector("#cross-right-dpc6-select"),
  crossLeftSearch: document.querySelector("#cross-left-search"),
  crossRightSearch: document.querySelector("#cross-right-search"),
  crossLeftResults: document.querySelector("#cross-left-results"),
  crossRightResults: document.querySelector("#cross-right-results"),
  crossLeftSelected: document.querySelector("#cross-left-selected"),
  crossRightSelected: document.querySelector("#cross-right-selected"),
  referenceDialog: document.querySelector("#reference-dialog"),
  referenceDialogTitle: document.querySelector("#reference-dialog-title"),
  referenceDialogBody: document.querySelector("#reference-dialog-body"),
  referenceDialogClose: document.querySelector("#reference-dialog-close"),
};

function formatCurrency(value) {
  return new Intl.NumberFormat("ja-JP", {
    style: "currency",
    currency: "JPY",
    maximumFractionDigits: 0,
  }).format(value || 0);
}

function formatSignedCurrency(value) {
  const amount = Math.round(value || 0);
  const sign = amount > 0 ? "+" : amount < 0 ? "-" : "±";
  return `${sign}${formatCurrency(Math.abs(amount))}`;
}

function html(strings, ...values) {
  return strings.reduce((acc, string, index) => `${acc}${string}${values[index] ?? ""}`, "");
}

function formatVersionBadge(entryOrVersion) {
  return `<span class="year-badge">令和${entryOrVersion.fiscalYear - 2018}年度</span>`;
}

function formatFiscalYearText(entryOrVersion) {
  return `令和${entryOrVersion.fiscalYear - 2018}年度`;
}

function formatVersionText(entryOrVersion) {
  return `${formatVersionBadge(entryOrVersion)} <span class="tag">${entryOrVersion.versionDate}</span>`;
}

function getVersions() {
  return [...(state.dataset?.versions || [])].sort((a, b) => {
    if (a.fiscalYear !== b.fiscalYear) {
      return b.fiscalYear - a.fiscalYear;
    }
    return b.versionDate.localeCompare(a.versionDate);
  });
}

function getEntriesForVersion(versionId) {
  return (state.dataset?.entries || []).filter((entry) => entry.versionId === versionId);
}

function cacheKey(versionId, dpcCode) {
  return `${versionId}:${dpcCode}`;
}

function getEntry(versionId, dpcCode) {
  const key = cacheKey(versionId, dpcCode);
  if (entryDetailCache.has(key)) {
    return entryDetailCache.get(key);
  }
  return (state.dataset?.entries || []).find((entry) => entry.versionId === versionId && entry.dpcCode === dpcCode);
}

async function ensureChunkLoaded(chunkPath) {
  if (!chunkPath) {
    return;
  }
  if (pendingChunkLoads.has(chunkPath)) {
    await pendingChunkLoads.get(chunkPath);
    return;
  }
  const request = fetch(`./data/${chunkPath}`, { cache: "no-store" })
    .then(async (response) => {
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${chunkPath}`);
      }
      const entries = await response.json();
      entries.forEach((entry) => {
        entryDetailCache.set(cacheKey(entry.versionId, entry.dpcCode), entry);
      });
    })
    .finally(() => {
      pendingChunkLoads.delete(chunkPath);
    });
  pendingChunkLoads.set(chunkPath, request);
  await request;
}

async function ensureEntry(versionId, dpcCode) {
  const existing = getEntry(versionId, dpcCode);
  if (!existing) {
    return null;
  }
  if (existing.curve) {
    return existing;
  }
  await ensureChunkLoaded(existing.chunkPath);
  return getEntry(versionId, dpcCode);
}

function applyPrimaryFilters(entries, filterState) {
  return entries.filter((entry) => {
    if (filterState.mdc && entry.mdcCode !== filterState.mdc) {
      return false;
    }
    if (filterState.dpc6 && entry.dpc6 !== filterState.dpc6) {
      return false;
    }
    return true;
  });
}

function getMdcOptions(entries) {
  const lookup = new Map();
  entries.forEach((entry) => {
    if (!lookup.has(entry.mdcCode)) {
      lookup.set(entry.mdcCode, `${entry.mdcCode} ${entry.mdcName || ""}`.trim());
    }
  });
  return [...lookup.entries()].sort((a, b) => a[0].localeCompare(b[0], "ja"));
}

function getDpc6Options(entries, selectedMdc) {
  const lookup = new Map();
  entries
    .filter((entry) => !selectedMdc || entry.mdcCode === selectedMdc)
    .forEach((entry) => {
      if (!lookup.has(entry.dpc6)) {
        lookup.set(entry.dpc6, `${entry.dpc6} ${entry.dpc6Name || ""}`.trim());
      }
    });
  return [...lookup.entries()].sort((a, b) => a[0].localeCompare(b[0], "ja"));
}

function getConditionLabel(condition) {
  return `${condition.label}:${condition.valueLabel}`;
}

function collectFilterOptions(entries) {
  const buckets = new Map();
  entries.forEach((entry) => {
    (entry.explicitConditions || []).forEach((condition) => {
      if (condition.fieldState === "explicit_none") {
        return;
      }
      const key = condition.canonicalKey;
      if (!buckets.has(key)) {
        buckets.set(key, {
          key,
          label: getConditionLabel(condition),
          group: condition.group,
          count: 0,
        });
      }
      buckets.get(key).count += 1;
    });
  });
  return [...buckets.values()].sort((a, b) => {
    if (a.group !== b.group) {
      return String(a.group).localeCompare(String(b.group), "ja");
    }
    return a.label.localeCompare(b.label, "ja");
  });
}

function entryMatchesFilters(entry, activeFilters) {
  if (!activeFilters.length) {
    return true;
  }
  const keys = new Set((entry.explicitConditions || []).map((condition) => condition.canonicalKey));
  return activeFilters.every((filterKey) => keys.has(filterKey));
}

function searchEntries(entries, term, activeFilters) {
  const query = term.trim().toLowerCase();
  return entries
    .filter((entry) => {
      if (!entryMatchesFilters(entry, activeFilters)) {
        return false;
      }
      if (!query) {
        return true;
      }
      const haystack = [
        entry.dpcCode,
        entry.diseaseName,
        entry.mdcCode,
        entry.mdcName,
        entry.dpc6Name,
        entry.label,
        entry.names?.canonicalName,
        entry.names?.legendName,
        entry.names?.detailName,
        ...(entry.explicitConditions || []).map((condition) => `${condition.label} ${condition.valueLabel}`),
      ]
        .join(" ")
        .toLowerCase();
      return haystack.includes(query);
    });
}

function visibleResults(entries, maxItems = 8) {
  return entries.slice(0, maxItems);
}

function seriesPointAtDay(series, day) {
  return series.curve.find((point) => point.day === day) || null;
}

function canUseStepChart() {
  return state.metric === "perDayYen";
}

function buildStepPath(curve, metric, xScale, yScale) {
  if (!curve.length) {
    return "";
  }
  const firstPoint = curve[0];
  let path = `M ${xScale(firstPoint.day).toFixed(2)} ${yScale(Number(firstPoint[metric]) || 0).toFixed(2)}`;
  for (let index = 1; index < curve.length; index += 1) {
    const current = curve[index - 1];
    const next = curve[index];
    const boundaryDay = (current.day + next.day) / 2;
    path += ` H ${xScale(boundaryDay).toFixed(2)} V ${yScale(Number(next[metric]) || 0).toFixed(2)}`;
  }
  path += ` H ${xScale(curve.at(-1).day).toFixed(2)}`;
  return path;
}

async function buildSeries() {
  if (!state.dataset) {
    return [];
  }

  if (state.mode === "standard") {
    const entries = await Promise.all(state.standardSelected.map((code) => ensureEntry(state.baseVersionId, code)));
    return entries
      .map((entry, index) => {
        if (!entry) {
          return null;
        }
        return {
          id: `${entry.versionId}:${entry.dpcCode}`,
          label: entry.names?.legendName || `${entry.dpcCode} ${entry.diseaseName}`,
          sublabel: `${entry.versionLabel} / ${entry.mdcCode} ${entry.mdcName || ""}`,
          color: palette[index % palette.length],
          dash: "",
          curve: entry.curve,
          entry,
        };
      })
      .filter(Boolean);
  }

  if (state.mode === "revision") {
    const lines = [];
    const pairs = await Promise.all(
      state.revisionSelected.map(async (code) => ({
        code,
        current: await ensureEntry(state.baseVersionId, code),
        historical: await ensureEntry(state.compareVersionId, code),
      }))
    );
    pairs.forEach(({ code, current, historical }, index) => {
      if (current) {
        lines.push({
          id: `${current.versionId}:${code}:base`,
          label: `${current.names?.legendName || code} 最新`,
          sublabel: current.versionLabel,
          color: palette[index % palette.length],
          dash: "",
          curve: current.curve,
          entry: current,
        });
      }
      if (historical) {
        lines.push({
          id: `${historical.versionId}:${code}:compare`,
          label: `${historical.names?.legendName || code} 比較版`,
          sublabel: historical.versionLabel,
          color: palette[index % palette.length],
          dash: "8 6",
          curve: historical.curve,
          entry: historical,
        });
      }
    });
    return lines;
  }

  const [left, right] = await Promise.all([
    ensureEntry(state.cross.leftVersionId, state.cross.leftCode),
    ensureEntry(state.cross.rightVersionId, state.cross.rightCode),
  ]);
  return [left, right]
    .map((entry, index) => {
      if (!entry) {
        return null;
      }
      return {
        id: `${entry.versionId}:${entry.dpcCode}:cross:${index}`,
        label: entry.names?.legendName || `${entry.dpcCode} ${entry.diseaseName}`,
        sublabel: `${entry.versionLabel}`,
        color: palette[index],
        dash: "",
        curve: entry.curve,
        entry,
      };
    })
    .filter(Boolean);
}

function renderVersionOptions(select, selectedValue) {
  const versions = getVersions();
  select.innerHTML = versions
    .map(
      (version) =>
        `<option value="${version.versionId}" ${version.versionId === selectedValue ? "selected" : ""}>${version.versionLabel}</option>`
    )
    .join("");
}

function renderCodeOptions(select, options, selectedValue, placeholder) {
  select.innerHTML = [
    `<option value="">${placeholder}</option>`,
    ...options.map(([value, label]) => `<option value="${value}" ${value === selectedValue ? "selected" : ""}>${label}</option>`),
  ].join("");
}

function renderConditionTags(conditions) {
  const visibleConditions = (conditions || []).filter((condition) => condition.fieldState !== "explicit_none");
  if (!visibleConditions.length) {
    return `<div class="meta-row"><span class="tag">条件なし</span></div>`;
  }
  return `<div class="condition-tags">${visibleConditions
    .map((condition) => `<span class="tag">${getConditionLabel(condition)}</span>`)
    .join("")}</div>`;
}

function renderSelected(container, selectedCodes, versionId, onRemove) {
  if (!selectedCodes.length) {
    container.innerHTML = `<div class="empty-state">未選択です。</div>`;
    return;
  }
  container.innerHTML = selectedCodes
    .map((code) => {
      const entry = getEntry(versionId, code);
      if (!entry) {
        return "";
      }
      return html`
        <div class="selected-item">
          <div>
            <strong>${entry.dpcCode}</strong><br>
            <span>${entry.names?.legendName || entry.diseaseName}</span>
            <div class="meta-row">${formatVersionText(entry)}</div>
            ${renderConditionTags(entry.explicitConditions)}
          </div>
          <button type="button" data-remove-code="${entry.dpcCode}">外す</button>
        </div>
      `;
    })
    .join("");
  container.querySelectorAll("[data-remove-code]").forEach((button) => {
    button.addEventListener("click", () => onRemove(button.dataset.removeCode));
  });
}

function renderLegend(seriesList) {
  if (!seriesList.length) {
    els.chartLegend.innerHTML = `<span class="tag">凡例は系列選択後に表示します。</span>`;
    return;
  }
  els.chartLegend.innerHTML = seriesList
    .map(
      (series) => html`
        <div class="legend-item">
          <svg class="legend-swatch" viewBox="0 0 32 8" aria-hidden="true">
            <line
              x1="0"
              y1="4"
              x2="32"
              y2="4"
              stroke="${series.color}"
              stroke-width="4"
              stroke-linecap="round"
              ${series.dash ? `stroke-dasharray="${series.dash}"` : ""}
            ></line>
          </svg>
          <span class="legend-label">${series.label}</span>
        </div>
      `
    )
    .join("");
}

function renderSearchResults(container, entries, onAdd) {
  if (!entries.length) {
    container.innerHTML = `<div class="empty-state">候補がありません。</div>`;
    return;
  }
  container.innerHTML = entries
    .map(
      (entry) => html`
        <div class="result-item">
          <div>
            <strong>${entry.dpcCode}</strong> ${entry.names?.legendName || entry.diseaseName}
          </div>
          <div class="meta-row">
            ${formatVersionBadge(entry)}
            <span class="tag">${entry.versionDate}</span>
            <span class="tag">${entry.mdcCode} ${entry.mdcName || "MDC未設定"}</span>
          </div>
          ${renderConditionTags(entry.explicitConditions)}
          <div class="detail-copy">
            ${entry.names?.detailName || entry.label}
          </div>
          <button type="button" data-add-code="${entry.dpcCode}" data-version="${entry.versionId}">追加</button>
        </div>
      `
    )
    .join("");
  container.querySelectorAll("[data-add-code]").forEach((button) => {
    button.addEventListener("click", () => onAdd(button.dataset.version, button.dataset.addCode));
  });
}

function shouldShowSuggestions(modeKey) {
  const searchText = state.searches[modeKey]?.trim();
  const filterCount = state.filters[modeKey]?.length || 0;
  const primary = state.primaryFilters[modeKey];
  return Boolean(searchText || filterCount || primary?.mdc || primary?.dpc6);
}

function renderCrossSelected(container, entry, side) {
  if (!entry) {
    container.textContent = "未選択";
    return;
  }
  container.innerHTML = html`
    <div>
      <strong>${entry.dpcCode}</strong><br>
      <span>${entry.names?.legendName || entry.diseaseName}</span>
      <div class="meta-row">${formatVersionText(entry)}</div>
      ${renderConditionTags(entry.explicitConditions)}
    </div>
    <button type="button" data-clear-side="${side}">クリア</button>
  `;
  container.querySelector("button").addEventListener("click", () => {
    state.cross[`${side}Code`] = "";
    state.hoverDay = null;
    render();
  });
}

function renderFilterPanel(modeKey, entries) {
  const config = filterConfig[modeKey];
  const activeFilters = state.filters[modeKey];
  const options = collectFilterOptions(entries).slice(0, 24);

  if (!config) {
    return;
  }

  const isOpen = state.filterPanels[modeKey] || activeFilters.length > 0;
  config.panel.classList.toggle("is-open", isOpen);
  config.toggle.classList.toggle("is-active", isOpen);
  config.toggle.textContent = activeFilters.length
    ? `条件で絞る (${activeFilters.length})`
    : "条件で絞る";

  config.active.innerHTML = activeFilters.length
    ? activeFilters
        .map((key) => {
          const option = options.find((item) => item.key === key) || { label: key };
          return `<button type="button" class="filter-chip is-active" data-filter-key="${key}">${option.label}</button>`;
        })
        .join("")
    : `<span class="tag">未設定</span>`;

  const groupedOptions = options.reduce((acc, option) => {
    const group = option.group || "modifier";
    if (!acc[group]) {
      acc[group] = [];
    }
    acc[group].push(option);
    return acc;
  }, {});

  config.groups.innerHTML = Object.entries(groupedOptions)
    .map(
      ([group, groupOptions]) => html`
        <div class="condition-group">
          <div class="condition-group-label">${conditionGroupLabels[group] || group}</div>
          <div class="condition-tags">
            ${groupOptions
              .map((option) => {
                const active = activeFilters.includes(option.key);
                return `<button type="button" class="condition-chip ${active ? "is-active" : ""}" data-mode-key="${modeKey}" data-filter-key="${option.key}">${option.label}</button>`;
              })
              .join("")}
          </div>
        </div>
      `
    )
    .join("");

  config.groups.querySelectorAll("[data-filter-key]").forEach((button) => {
    button.addEventListener("click", () => {
      toggleFilter(modeKey, button.dataset.filterKey);
    });
  });
  config.active.querySelectorAll("[data-filter-key]").forEach((button) => {
    button.addEventListener("click", () => {
      toggleFilter(modeKey, button.dataset.filterKey);
    });
  });
}

function toggleFilter(modeKey, filterKey) {
  const current = state.filters[modeKey];
  state.filters[modeKey] = current.includes(filterKey)
    ? current.filter((item) => item !== filterKey)
    : [...current, filterKey];
  if (state.filters[modeKey].length) {
    state.filterPanels[modeKey] = true;
  }
  state.hoverDay = null;
  render();
}

function resetModeState(nextMode) {
  state.mode = nextMode;
  state.hoverDay = null;
  state.standardSelected = [];
  state.revisionSelected = [];
  state.cross.leftCode = "";
  state.cross.rightCode = "";
  state.searches.standard = "";
  state.searches.revision = "";
  state.searches.crossLeft = "";
  state.searches.crossRight = "";
  state.filters.standard = [];
  state.filters.revision = [];
  state.filters.crossLeft = [];
  state.filters.crossRight = [];
  state.primaryFilters.standard = { mdc: "", dpc6: "" };
  state.primaryFilters.revision = { mdc: "", dpc6: "" };
  state.primaryFilters.crossLeft = { mdc: "", dpc6: "" };
  state.primaryFilters.crossRight = { mdc: "", dpc6: "" };
  state.filterPanels.standard = false;
  state.filterPanels.revision = false;
  state.filterPanels.crossLeft = false;
  state.filterPanels.crossRight = false;
  els.searchInput.value = "";
  els.revisionSearchInput.value = "";
  els.crossLeftSearch.value = "";
  els.crossRightSearch.value = "";
}

function renderChart(seriesList) {
  const svg = els.chart;
  const useStepChart = canUseStepChart();
  if (!seriesList.length) {
    renderLegend([]);
    svg.innerHTML = `
      <rect x="0" y="0" width="960" height="520" fill="transparent"></rect>
      <text x="480" y="250" text-anchor="middle" fill="#586564" font-size="22">表示する系列を選択してください</text>
    `;
    els.hoverCard.textContent = "マウスを重ねると日別の値を表示します。";
    return;
  }
  renderLegend(seriesList);

  const margin = { top: 28, right: 28, bottom: 48, left: 88 };
  const width = 960 - margin.left - margin.right;
  const height = 520 - margin.top - margin.bottom;
  const maxDay = Math.max(...seriesList.map((series) => series.curve.at(-1)?.day || 1));
  const maxValue = Math.max(
    ...seriesList.flatMap((series) => series.curve.map((point) => Number(point[state.metric]) || 0)),
    1
  );
  const yMax = maxValue * 1.08;

  const xScale = (day) => margin.left + ((day - 1) / Math.max(maxDay - 1, 1)) * width;
  const yScale = (value) => margin.top + height - (value / yMax) * height;

  const yTicks = Array.from({ length: 5 }, (_, index) => (yMax / 4) * index).reverse();
  const xTicks = Array.from({ length: Math.min(8, maxDay) }, (_, index) => {
    if (maxDay === 1) {
      return 1;
    }
    return Math.round(1 + (index * (maxDay - 1)) / Math.max(Math.min(8, maxDay) - 1, 1));
  });

  const lineMarkup = seriesList
    .map((series) => {
      const dashAttr = series.dash ? `stroke-dasharray="${series.dash}"` : "";
      if (useStepChart) {
        const path = buildStepPath(series.curve, state.metric, xScale, yScale);
        return `<path fill="none" stroke="${series.color}" stroke-width="4" stroke-linejoin="miter" stroke-linecap="square" ${dashAttr} d="${path}"></path>`;
      }
      const points = series.curve
        .map((point) => `${xScale(point.day).toFixed(2)},${yScale(Number(point[state.metric]) || 0).toFixed(2)}`)
        .join(" ");
      return `<polyline fill="none" stroke="${series.color}" stroke-width="4" stroke-linejoin="round" stroke-linecap="round" ${dashAttr} points="${points}"></polyline>`;
    })
    .join("");

  const hoverDay = state.hoverDay ?? maxDay;
  const hoverX = xScale(Math.min(Math.max(hoverDay, 1), maxDay));
  const hoverMarkup =
    state.hoverDay !== null
      ? `<line x1="${hoverX}" y1="${margin.top}" x2="${hoverX}" y2="${margin.top + height}" stroke="rgba(31,42,42,0.3)" stroke-dasharray="4 4"></line>`
      : "";

  svg.innerHTML = `
    <rect x="0" y="0" width="960" height="520" fill="transparent"></rect>
    ${yTicks
      .map(
        (tick) => `
          <line x1="${margin.left}" y1="${yScale(tick)}" x2="${margin.left + width}" y2="${yScale(tick)}" stroke="rgba(31,42,42,0.08)"></line>
          <text x="${margin.left - 16}" y="${yScale(tick) + 5}" text-anchor="end" fill="#586564" font-size="14">${Math.round(tick).toLocaleString()}</text>
        `
      )
      .join("")}
    ${xTicks
      .map(
        (tick) => `
          <line x1="${xScale(tick)}" y1="${margin.top + height}" x2="${xScale(tick)}" y2="${margin.top + height + 8}" stroke="rgba(31,42,42,0.18)"></line>
          <text x="${xScale(tick)}" y="${margin.top + height + 28}" text-anchor="middle" fill="#586564" font-size="14">${tick}日</text>
        `
      )
      .join("")}
    <line x1="${margin.left}" y1="${margin.top}" x2="${margin.left}" y2="${margin.top + height}" stroke="rgba(31,42,42,0.2)" />
    <line x1="${margin.left}" y1="${margin.top + height}" x2="${margin.left + width}" y2="${margin.top + height}" stroke="rgba(31,42,42,0.2)" />
    ${hoverMarkup}
    ${lineMarkup}
    <rect id="chart-overlay" x="${margin.left}" y="${margin.top}" width="${width}" height="${height}" fill="transparent"></rect>
  `;

  const overlay = svg.querySelector("#chart-overlay");
  overlay.addEventListener("mousemove", (event) => {
    const rect = svg.getBoundingClientRect();
    const x = ((event.clientX - rect.left) / rect.width) * 960;
    const ratio = (x - margin.left) / width;
    const day = Math.round(1 + Math.min(Math.max(ratio, 0), 1) * Math.max(maxDay - 1, 0));
    state.hoverDay = day;
    renderHover(seriesList, day);
    renderChart(seriesList);
  });
  overlay.addEventListener("mouseleave", () => {
    state.hoverDay = null;
    els.hoverCard.textContent = "マウスを重ねると日別の値を表示します。";
    renderChart(seriesList);
  });

  renderHover(seriesList, hoverDay);
}

function renderChartLoading() {
  renderLegend([]);
  els.chart.innerHTML = `
    <rect x="0" y="0" width="960" height="520" fill="transparent"></rect>
    <text x="480" y="250" text-anchor="middle" fill="#586564" font-size="22">データを読み込んでいます</text>
  `;
  els.hoverCard.textContent = "系列データを取得中です。";
}

function renderChartError(message) {
  renderLegend([]);
  els.chart.innerHTML = `
    <rect x="0" y="0" width="960" height="520" fill="transparent"></rect>
    <text x="480" y="240" text-anchor="middle" fill="#9b2226" font-size="20">系列データの読込に失敗しました</text>
    <text x="480" y="275" text-anchor="middle" fill="#586564" font-size="14">${message}</text>
  `;
  els.hoverCard.textContent = "系列データの読込に失敗しました。";
}

function renderHover(seriesList, day) {
  const pointRows = seriesList
    .map((series) => {
      const point = seriesPointAtDay(series, day);
      if (!point) {
        return null;
      }
      return {
        series,
        point,
        text: `${formatFiscalYearText(series.entry)} ${series.entry.versionDate}: ${series.label} ${formatCurrency(point[state.metric])}`,
      };
    })
    .filter(Boolean);
  if (!pointRows.length) {
    els.hoverCard.textContent = "選択した系列のこの日付点はありません。";
    return;
  }

  const diffRows = [];
  if (state.mode === "crossYear" && pointRows.length >= 2) {
    const left = pointRows[0];
    const right = pointRows[1];
    diffRows.push(`差額（左-右） ${formatSignedCurrency(left.point[state.metric] - right.point[state.metric])}`);
  }

  if (state.mode === "revision") {
    const pairMap = new Map();
    pointRows.forEach((row) => {
      const code = row.series.entry.dpcCode;
      if (!pairMap.has(code)) {
        pairMap.set(code, {});
      }
      if (row.series.id.endsWith(":base")) {
        pairMap.get(code).base = row;
      }
      if (row.series.id.endsWith(":compare")) {
        pairMap.get(code).compare = row;
      }
    });
    pairMap.forEach((pair, code) => {
      if (pair.base && pair.compare) {
        diffRows.push(
          `${code} 差額（最新-比較版） ${formatSignedCurrency(pair.base.point[state.metric] - pair.compare.point[state.metric])}`
        );
      }
    });
  }

  els.hoverCard.innerHTML = `
    <div class="hover-lines">
      ${pointRows
        .map(
          (row, index) => `
            <div class="hover-line">
              <span class="${index === 0 ? "hover-day-label" : "hover-day-spacer"}">${index === 0 ? `${day}日目` : ""}</span>
              <span>${row.text}</span>
            </div>
          `
        )
        .join("")}
      ${diffRows
        .map(
          (line) => `
            <div class="hover-line">
              <span class="hover-day-spacer"></span>
              <span class="hover-diff">${line}</span>
            </div>
          `
        )
        .join("")}
    </div>
  `;
}

function renderSummary(seriesList) {
  if (!seriesList.length) {
    els.seriesSummary.innerHTML = `<div class="empty-state">系列を追加すると、期間境界と最終到達金額を表示します。</div>`;
    return;
  }
  const cards = seriesList.map((series) => {
    const lastPoint = series.curve.at(-1);
    const p1 = series.entry.periods[0];
    const p2 = series.entry.periods[1];
    const p3 = series.entry.periods[2];
    return html`
      <div class="summary-card">
        <h3 style="color:${series.color}">${series.label}</h3>
        <div class="meta-row">${formatVersionText(series.entry)} <span class="tag">${series.sublabel}</span></div>
        ${renderConditionTags(series.entry.explicitConditions)}
        <p>最終累積: ${formatCurrency(lastPoint?.cumulativeYen || 0)}</p>
        <p>期間1: 1-${p1.endDay}日 / ${formatCurrency(p1.yen)}</p>
        <p>期間2: ${p2.startDay}-${p2.endDay}日 / ${formatCurrency(p2.yen)}</p>
        <p>期間3: ${p3.startDay}-${p3.endDay}日 / ${formatCurrency(p3.yen)}</p>
        <div class="summary-actions">
          <button type="button" class="summary-link" data-reference-id="${series.id}">手術・処置・副傷病</button>
        </div>
      </div>
    `;
  });
  els.seriesSummary.innerHTML = cards.join("");
  els.seriesSummary.querySelectorAll("[data-reference-id]").forEach((button) => {
    button.addEventListener("click", () => {
      const series = seriesList.find((item) => item.id === button.dataset.referenceId);
      if (series) {
        openReferenceDialog(series.entry);
      }
    });
  });
}

function renderReferenceSection(title, payload) {
  if (!payload || !payload.items?.length) {
    return "";
  }
  return html`
    <section class="reference-section">
      <h3>${title}</h3>
      <div class="meta-row">
        ${payload.codes ? `<span class="tag">codes:${payload.codes.join(", ")}</span>` : ""}
        ${payload.flag ? `<span class="tag">flag:${payload.flag}</span>` : ""}
        ${payload.flags ? `<span class="tag">flags:${payload.flags.join(", ")}</span>` : ""}
        ${payload.label ? `<span class="tag">${payload.label}</span>` : ""}
      </div>
      <div class="reference-list">
        ${payload.items
          .map(
            (item) => html`
              <div class="reference-item">
                <strong>${item.name}</strong><br>
                <span>${item.code || "コードなし"}</span>
              </div>
            `
          )
          .join("")}
      </div>
    </section>
  `;
}

function openReferenceDialog(entry) {
  const details = entry.referenceDetails || {};
  els.referenceDialogTitle.textContent = `${entry.dpcCode} ${entry.names?.legendName || entry.diseaseName}`;
  const markup = [
    renderReferenceSection("手術", details.surgery),
    renderReferenceSection("手術・処置等1", details.procedure1),
    renderReferenceSection("手術・処置等2", details.procedure2),
    renderReferenceSection("定義副傷病", details.comorbidity),
  ]
    .filter(Boolean)
    .join("");
  els.referenceDialogBody.innerHTML = markup || `<div class="empty-state">参照できる詳細がありません。</div>`;
  els.referenceDialog.showModal();
}

function serializeState() {
  const params = new URLSearchParams();
  params.set("mode", state.mode);
  params.set("metric", state.metric);
  if (state.baseVersionId) params.set("base", state.baseVersionId);
  if (state.compareVersionId) params.set("compare", state.compareVersionId);
  if (state.standardSelected.length) params.set("std", state.standardSelected.join(","));
  if (state.revisionSelected.length) params.set("rev", state.revisionSelected.join(","));
  if (state.cross.leftVersionId) params.set("leftVersion", state.cross.leftVersionId);
  if (state.cross.rightVersionId) params.set("rightVersion", state.cross.rightVersionId);
  if (state.cross.leftCode) params.set("leftCode", state.cross.leftCode);
  if (state.cross.rightCode) params.set("rightCode", state.cross.rightCode);
  Object.entries(state.searches).forEach(([key, value]) => {
    if (value) params.set(`search_${key}`, value);
  });
  Object.entries(state.primaryFilters).forEach(([key, value]) => {
    if (value.mdc) params.set(`mdc_${key}`, value.mdc);
    if (value.dpc6) params.set(`dpc6_${key}`, value.dpc6);
  });
  Object.entries(state.filters).forEach(([key, values]) => {
    if (values.length) params.set(`filters_${key}`, values.join(","));
  });
  return `${window.location.pathname}?${params.toString()}`;
}

function restoreStateFromUrl() {
  const params = new URLSearchParams(window.location.search);
  state.mode = params.get("mode") || state.mode;
  state.metric = params.get("metric") || state.metric;
  state.baseVersionId = params.get("base") || state.baseVersionId;
  state.compareVersionId = params.get("compare") || state.compareVersionId;
  state.standardSelected = (params.get("std") || "").split(",").filter(Boolean);
  state.revisionSelected = (params.get("rev") || "").split(",").filter(Boolean);
  state.cross.leftVersionId = params.get("leftVersion") || state.cross.leftVersionId;
  state.cross.rightVersionId = params.get("rightVersion") || state.cross.rightVersionId;
  state.cross.leftCode = params.get("leftCode") || state.cross.leftCode;
  state.cross.rightCode = params.get("rightCode") || state.cross.rightCode;
  Object.keys(state.searches).forEach((key) => {
    state.searches[key] = params.get(`search_${key}`) || "";
  });
  Object.keys(state.primaryFilters).forEach((key) => {
    state.primaryFilters[key] = {
      mdc: params.get(`mdc_${key}`) || "",
      dpc6: params.get(`dpc6_${key}`) || "",
    };
  });
  Object.keys(state.filters).forEach((key) => {
    state.filters[key] = (params.get(`filters_${key}`) || "").split(",").filter(Boolean);
    state.filterPanels[key] = state.filters[key].length > 0;
  });
}

function syncUrlState() {
  const nextUrl = serializeState();
  window.history.replaceState({}, "", nextUrl);
}

function syncControlVisibility() {
  [
    [els.standardControls, state.mode === "standard"],
    [els.revisionControls, state.mode === "revision"],
    [els.crossYearControls, state.mode === "crossYear"],
  ].forEach(([element, active]) => {
    element.classList.toggle("is-hidden", !active);
    element.classList.toggle("is-active-panel", active);
  });
}

function renderSearchPanels() {
  const baseEntries = getEntriesForVersion(state.baseVersionId);
  const standardBase = applyPrimaryFilters(baseEntries, state.primaryFilters.standard);
  const standardEntries = searchEntries(standardBase, state.searches.standard, state.filters.standard);
  renderCodeOptions(els.standardMdcSelect, getMdcOptions(baseEntries), state.primaryFilters.standard.mdc, "すべてのMDC");
  renderCodeOptions(
    els.standardDpc6Select,
    getDpc6Options(baseEntries, state.primaryFilters.standard.mdc),
    state.primaryFilters.standard.dpc6,
    "すべてのDPC6"
  );
  renderFilterPanel("standard", standardBase);
  renderSelected(els.selectedStandard, state.standardSelected, state.baseVersionId, (code) => {
    state.standardSelected = state.standardSelected.filter((item) => item !== code);
    state.hoverDay = null;
    render();
  });
  if (shouldShowSuggestions("standard")) {
    renderSearchResults(els.searchResults, visibleResults(standardEntries), (_version, code) => {
      if (!state.standardSelected.includes(code)) {
        state.standardSelected = [...state.standardSelected, code].slice(0, 8);
        state.hoverDay = null;
        render();
      }
    });
  } else {
    els.searchResults.innerHTML = `<div class="empty-state">MDC・DPC6・病名で絞ると候補を表示します。</div>`;
  }

  const revisionBaseEntries = getEntriesForVersion(state.baseVersionId);
  const revisionBase = applyPrimaryFilters(revisionBaseEntries, state.primaryFilters.revision);
  const revisionEntries = searchEntries(revisionBase, state.searches.revision, state.filters.revision);
  renderCodeOptions(els.revisionMdcSelect, getMdcOptions(revisionBaseEntries), state.primaryFilters.revision.mdc, "すべてのMDC");
  renderCodeOptions(
    els.revisionDpc6Select,
    getDpc6Options(revisionBaseEntries, state.primaryFilters.revision.mdc),
    state.primaryFilters.revision.dpc6,
    "すべてのDPC6"
  );
  renderFilterPanel("revision", revisionBase);
  renderSelected(els.selectedRevision, state.revisionSelected, state.baseVersionId, (code) => {
    state.revisionSelected = state.revisionSelected.filter((item) => item !== code);
    state.hoverDay = null;
    render();
  });
  if (shouldShowSuggestions("revision")) {
    renderSearchResults(els.revisionResults, visibleResults(revisionEntries), (_version, code) => {
      if (!state.revisionSelected.includes(code)) {
        state.revisionSelected = [...state.revisionSelected, code].slice(0, 6);
        state.hoverDay = null;
        render();
      }
    });
  } else {
    els.revisionResults.innerHTML = `<div class="empty-state">MDC・DPC6・DPCコードで絞ると候補を表示します。</div>`;
  }

  const leftEntries = getEntriesForVersion(state.cross.leftVersionId);
  const rightEntries = getEntriesForVersion(state.cross.rightVersionId);
  const leftBase = applyPrimaryFilters(leftEntries, state.primaryFilters.crossLeft);
  const rightBase = applyPrimaryFilters(rightEntries, state.primaryFilters.crossRight);
  const leftResults = searchEntries(leftBase, state.searches.crossLeft, state.filters.crossLeft);
  const rightResults = searchEntries(rightBase, state.searches.crossRight, state.filters.crossRight);
  renderCodeOptions(els.crossLeftMdcSelect, getMdcOptions(leftEntries), state.primaryFilters.crossLeft.mdc, "すべてのMDC");
  renderCodeOptions(
    els.crossLeftDpc6Select,
    getDpc6Options(leftEntries, state.primaryFilters.crossLeft.mdc),
    state.primaryFilters.crossLeft.dpc6,
    "すべてのDPC6"
  );
  renderCodeOptions(els.crossRightMdcSelect, getMdcOptions(rightEntries), state.primaryFilters.crossRight.mdc, "すべてのMDC");
  renderCodeOptions(
    els.crossRightDpc6Select,
    getDpc6Options(rightEntries, state.primaryFilters.crossRight.mdc),
    state.primaryFilters.crossRight.dpc6,
    "すべてのDPC6"
  );
  renderFilterPanel("crossLeft", leftBase);
  renderFilterPanel("crossRight", rightBase);
  if (shouldShowSuggestions("crossLeft")) {
    renderSearchResults(els.crossLeftResults, visibleResults(leftResults, 6), (_version, code) => {
      state.cross.leftCode = code;
      state.hoverDay = null;
      render();
    });
  } else {
    els.crossLeftResults.innerHTML = `<div class="empty-state">左側の条件を指定してください。</div>`;
  }
  if (shouldShowSuggestions("crossRight")) {
    renderSearchResults(els.crossRightResults, visibleResults(rightResults, 6), (_version, code) => {
      state.cross.rightCode = code;
      state.hoverDay = null;
      render();
    });
  } else {
    els.crossRightResults.innerHTML = `<div class="empty-state">右側の条件を指定してください。</div>`;
  }

  renderCrossSelected(els.crossLeftSelected, getEntry(state.cross.leftVersionId, state.cross.leftCode), "left");
  renderCrossSelected(els.crossRightSelected, getEntry(state.cross.rightVersionId, state.cross.rightCode), "right");
}

async function render() {
  if (!state.dataset) {
    return;
  }
  const renderToken = ++state.renderToken;
  state.loadError = "";
  try {
    document.querySelectorAll("#mode-buttons button").forEach((button) => {
      button.classList.toggle("is-active", button.dataset.mode === state.mode);
    });
    document.querySelectorAll("#metric-buttons button").forEach((button) => {
      button.classList.toggle("is-active", button.dataset.metric === state.metric);
    });
    syncControlVisibility();

    const versions = getVersions();
    if (!state.baseVersionId && versions.length) {
      state.baseVersionId = versions[0].versionId;
    }
    if (!state.compareVersionId && versions.length > 1) {
      state.compareVersionId = versions[1].versionId;
    }
    if (!state.cross.leftVersionId && versions.length) {
      state.cross.leftVersionId = versions[0].versionId;
    }
    if (!state.cross.rightVersionId && versions.length > 1) {
      state.cross.rightVersionId = versions[1].versionId;
    }

    renderVersionOptions(els.baseVersionSelect, state.baseVersionId);
    renderVersionOptions(els.revisionBaseSelect, state.baseVersionId);
    renderVersionOptions(els.revisionCompareSelect, state.compareVersionId);
    renderVersionOptions(els.crossLeftVersion, state.cross.leftVersionId);
    renderVersionOptions(els.crossRightVersion, state.cross.rightVersionId);
    els.searchInput.value = state.searches.standard;
    els.revisionSearchInput.value = state.searches.revision;
    els.crossLeftSearch.value = state.searches.crossLeft;
    els.crossRightSearch.value = state.searches.crossRight;

    renderSearchPanels();
    renderChartLoading();
    els.seriesSummary.innerHTML = `<div class="empty-state">系列データを読み込んでいます。</div>`;

    const seriesList = await buildSeries();
    if (renderToken !== state.renderToken) {
      return;
    }
    els.chartSubtitle.textContent = "";
    renderChart(seriesList);
    renderSummary(seriesList);
    syncUrlState();
  } catch (error) {
    if (renderToken !== state.renderToken) {
      return;
    }
    console.error(error);
    state.loadError = error instanceof Error ? error.message : String(error);
    els.chartSubtitle.textContent = "系列データの読込に失敗しました";
    renderChartError(state.loadError);
    els.seriesSummary.innerHTML = `<div class="empty-state">系列データの読込に失敗しました。<br>${state.loadError}</div>`;
  }
}

function attachEvents() {
  document.querySelectorAll("#mode-buttons button").forEach((button) => {
    button.addEventListener("click", () => {
      resetModeState(button.dataset.mode);
      document.querySelectorAll("#mode-buttons button").forEach((item) => item.classList.toggle("is-active", item === button));
      render();
    });
  });

  document.querySelectorAll("#metric-buttons button").forEach((button) => {
    button.addEventListener("click", () => {
      state.metric = button.dataset.metric;
      state.hoverDay = null;
      document.querySelectorAll("#metric-buttons button").forEach((item) => item.classList.toggle("is-active", item === button));
      render();
    });
  });

  els.baseVersionSelect.addEventListener("change", (event) => {
    state.baseVersionId = event.target.value;
    state.standardSelected = [];
    state.filters.standard = [];
    state.primaryFilters.standard = { mdc: "", dpc6: "" };
    state.filterPanels.standard = false;
    state.hoverDay = null;
    render();
  });
  els.revisionBaseSelect.addEventListener("change", (event) => {
    state.baseVersionId = event.target.value;
    state.revisionSelected = [];
    state.filters.revision = [];
    state.primaryFilters.revision = { mdc: "", dpc6: "" };
    state.filterPanels.revision = false;
    state.hoverDay = null;
    render();
  });
  els.revisionCompareSelect.addEventListener("change", (event) => {
    state.compareVersionId = event.target.value;
    state.hoverDay = null;
    render();
  });
  els.crossLeftVersion.addEventListener("change", (event) => {
    state.cross.leftVersionId = event.target.value;
    state.cross.leftCode = "";
    state.filters.crossLeft = [];
    state.primaryFilters.crossLeft = { mdc: "", dpc6: "" };
    state.filterPanels.crossLeft = false;
    state.hoverDay = null;
    render();
  });
  els.crossRightVersion.addEventListener("change", (event) => {
    state.cross.rightVersionId = event.target.value;
    state.cross.rightCode = "";
    state.filters.crossRight = [];
    state.primaryFilters.crossRight = { mdc: "", dpc6: "" };
    state.filterPanels.crossRight = false;
    state.hoverDay = null;
    render();
  });

  els.searchInput.addEventListener("input", (event) => {
    state.searches.standard = event.target.value;
    render();
  });
  els.revisionSearchInput.addEventListener("input", (event) => {
    state.searches.revision = event.target.value;
    render();
  });
  els.crossLeftSearch.addEventListener("input", (event) => {
    state.searches.crossLeft = event.target.value;
    render();
  });
  els.crossRightSearch.addEventListener("input", (event) => {
    state.searches.crossRight = event.target.value;
    render();
  });

  [
    ["standard", els.standardMdcSelect, "mdc"],
    ["standard", els.standardDpc6Select, "dpc6"],
    ["revision", els.revisionMdcSelect, "mdc"],
    ["revision", els.revisionDpc6Select, "dpc6"],
    ["crossLeft", els.crossLeftMdcSelect, "mdc"],
    ["crossLeft", els.crossLeftDpc6Select, "dpc6"],
    ["crossRight", els.crossRightMdcSelect, "mdc"],
    ["crossRight", els.crossRightDpc6Select, "dpc6"],
  ].forEach(([modeKey, element, field]) => {
    element.addEventListener("change", (event) => {
      state.primaryFilters[modeKey][field] = event.target.value;
      if (field === "mdc") {
        state.primaryFilters[modeKey].dpc6 = "";
      }
      state.hoverDay = null;
      render();
    });
  });

  Object.entries(filterConfig).forEach(([modeKey, config]) => {
    config.toggle.addEventListener("click", () => {
      state.filterPanels[modeKey] = !state.filterPanels[modeKey];
      render();
    });
    config.clear.addEventListener("click", () => {
      state.filters[modeKey] = [];
      state.filterPanels[modeKey] = false;
      render();
    });
  });

  els.referenceDialogClose.addEventListener("click", () => {
    els.referenceDialog.close();
  });
  els.referenceDialog.addEventListener("click", (event) => {
    const rect = els.referenceDialog.getBoundingClientRect();
    const inside =
      rect.top <= event.clientY &&
      event.clientY <= rect.top + rect.height &&
      rect.left <= event.clientX &&
      event.clientX <= rect.left + rect.width;
    if (!inside) {
      els.referenceDialog.close();
    }
  });

}

async function initialize() {
  try {
    const response = await fetch("./data/index.json", { cache: "no-store" });
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    state.dataset = await response.json();
    restoreStateFromUrl();
    const versions = getVersions();
    if (versions.length) {
      const latest = versions[0];
      els.datasetStatus.textContent = latest.versionLabel;
      els.datasetMeta.textContent = latest.sourcePageLabel || "掲載ページ";
      els.datasetSourceLink.hidden = false;
      els.datasetSourceLink.href = "./guide.html";
      els.datasetSourceLink.textContent = "使い方・免責事項";
    } else {
      els.datasetStatus.textContent = "版情報なし";
      els.datasetMeta.textContent = "データ未生成";
      els.datasetSourceLink.hidden = true;
    }
    render();
  } catch (error) {
    els.datasetStatus.textContent = "データ未生成";
    els.datasetMeta.textContent = "README の手順で JSON を生成してください。";
    els.datasetSourceLink.hidden = true;
    console.error(error);
  }
}

attachEvents();
initialize();
