const STORAGE_KEY = "obs-baseball-scoreboard-state";
const serverMode = location.protocol === "http:" || location.protocol === "https:";
const HOME_RUN_DURATION_MS = 3000;
let homeRunHideTimer = null;
const fieldPositions = [
  { label: "1-P", name: "Pitcher" },
  { label: "2-C", name: "Catcher" },
  { label: "3-1B", name: "First base" },
  { label: "4-2B", name: "Second base" },
  { label: "5-3B", name: "Third base" },
  { label: "6-SS", name: "Shortstop" },
  { label: "7-LF", name: "Left field" },
  { label: "8-CF", name: "Center field" },
  { label: "9-RF", name: "Right field" }
];

function emptyLineup() {
  return {
    starters: Array.from({ length: 9 }, () => ""),
    positions: fieldPositions.map((position) => position.label.split("-")[1]),
    battingOrder: Array.from({ length: 9 }, (_, index) => index + 1),
    dh: { name: "", replaces: 1 },
    reserves: ""
  };
}

const defaults = {
  eventTitle: "LIVE BASEBALL",
  venue: "Campo centrale",
  language: "it",
  inning: 1,
  half: "top",
  balls: 0,
  strikes: 0,
  outs: 0,
  inningRuns: {
    away: [],
    home: []
  },
  bases: {
    first: false,
    second: false,
    third: false
  },
  pitcher: "Pitcher",
  pitchCount: 0,
  pitcherPitchCounts: {
    Pitcher: 0
  },
  lineupOverlay: {
    visible: false,
    team: "away"
  },
  homeRunEvent: null,
  lineups: {
    away: emptyLineup(),
    home: emptyLineup()
  },
  savedLineups: {},
  showPitcher: true,
  teams: {
    away: {
      name: "Ospiti",
      abbr: "AWY",
      score: 0,
      hits: 0,
      primary: "#174b8a",
      text: "#ffffff",
      logo: ""
    },
    home: {
      name: "Casa",
      abbr: "HME",
      score: 0,
      hits: 0,
      primary: "#b82034",
      text: "#ffffff",
      logo: ""
    }
  }
};

const translations = {
  it: {
    scoreboardController: "Controller scoreboard",
    lineupController: "Controller lineup",
    openOverlay: "Apri overlay",
    openLineScore: "Apri line score",
    openLineupOverlay: "Apri lineup overlay",
    resetGame: "Reset partita",
    teamName: "Nome team",
    abbr: "Sigla",
    baseColor: "Colore base",
    baseCode: "Codice base",
    textColor: "Colore testo",
    textCode: "Codice testo",
    inPlay: "In play",
    homeRun: "Home Run",
    bases: "Basi",
    advance: "Avanza",
    clear: "Svuota",
    newPitcher: "Nuovo pitcher",
    newPitcherName: "Nuovo nome pitcher",
    addZeroPitch: "Inserisci con 0 Pitch",
    eventTitle: "Titolo evento",
    venue: "Campo / note",
    showPitcher: "Mostra riga pitcher",
    showAway: "Mostra Away",
    showHome: "Mostra Home",
    hide: "Nascondi",
    lineupOverlay: "Overlay lineup",
    save: "Salva",
    load: "Carica",
    reserves: "Riserve",
    oneNamePerLine: "Un nome per riga",
    savedLineups: "Lineup salvate",
    defensivePosition: "Posizione",
    playerName: "Giocatore",
    battingOrder: "Ordine battuta",
    battingOrReplacement: "Ordine / Sostituisce",
    replacesPosition: "Sostituisce",
    offensiveLineup: "Lineup di battuta",
    hidden: "Nascosto",
    showing: "Mostra",
    pitches: "Pitch",
    resetConfirm: "Confermi il reset della partita? Verranno azzerati punteggio, inning, basi, Pitch e storico pitcher."
  },
  en: {
    scoreboardController: "Scoreboard controller",
    lineupController: "Lineup controller",
    openOverlay: "Open overlay",
    openLineScore: "Open line score",
    openLineupOverlay: "Open lineup overlay",
    resetGame: "Reset game",
    teamName: "Team name",
    abbr: "Abbreviation",
    baseColor: "Base color",
    baseCode: "Base code",
    textColor: "Text color",
    textCode: "Text code",
    inPlay: "In play",
    homeRun: "Home Run",
    bases: "Bases",
    advance: "Advance",
    clear: "Clear",
    newPitcher: "New pitcher",
    newPitcherName: "New pitcher name",
    addZeroPitch: "Add with 0 pitches",
    eventTitle: "Event title",
    venue: "Field / notes",
    showPitcher: "Show pitcher row",
    showAway: "Show Away",
    showHome: "Show Home",
    hide: "Hide",
    lineupOverlay: "Lineup overlay",
    save: "Save",
    load: "Load",
    reserves: "Reserves",
    oneNamePerLine: "One name per line",
    savedLineups: "Saved lineups",
    defensivePosition: "Position",
    playerName: "Player",
    battingOrder: "Batting order",
    battingOrReplacement: "Order / Replaces",
    replacesPosition: "Replaces",
    offensiveLineup: "Batting lineup",
    hidden: "Hidden",
    showing: "Showing",
    pitches: "pitches",
    resetConfirm: "Confirm game reset? Score, inning, bases, pitches and pitcher history will be cleared."
  },
  es: {
    scoreboardController: "Control del marcador",
    lineupController: "Control del lineup",
    openOverlay: "Abrir overlay",
    openLineScore: "Abrir line score",
    openLineupOverlay: "Abrir overlay lineup",
    resetGame: "Reiniciar juego",
    teamName: "Nombre del equipo",
    abbr: "Sigla",
    baseColor: "Color base",
    baseCode: "Codigo base",
    textColor: "Color texto",
    textCode: "Codigo texto",
    inPlay: "En juego",
    homeRun: "Home Run",
    bases: "Bases",
    advance: "Avanzar",
    clear: "Limpiar",
    newPitcher: "Nuevo pitcher",
    newPitcherName: "Nombre nuevo pitcher",
    addZeroPitch: "Agregar con 0 pitches",
    eventTitle: "Titulo evento",
    venue: "Campo / notas",
    showPitcher: "Mostrar fila pitcher",
    showAway: "Mostrar Away",
    showHome: "Mostrar Home",
    hide: "Ocultar",
    lineupOverlay: "Overlay lineup",
    save: "Guardar",
    load: "Cargar",
    reserves: "Reservas",
    oneNamePerLine: "Un nombre por linea",
    savedLineups: "Lineups guardados",
    defensivePosition: "Posicion",
    playerName: "Jugador",
    battingOrder: "Orden al bate",
    battingOrReplacement: "Orden / Sustituye",
    replacesPosition: "Sustituye",
    offensiveLineup: "Lineup de bateo",
    hidden: "Oculto",
    showing: "Mostrando",
    pitches: "pitches",
    resetConfirm: "Confirmar reinicio del juego? Se borraran score, inning, bases, pitches e historial pitcher."
  }
};

const channel = "BroadcastChannel" in window ? new BroadcastChannel("scoreboard") : null;
let state = loadState();
let isApplyingRemoteState = false;
let lastRemoteState = "";
let appLicense = {
  valid: true,
  licensee: "",
  expires: "",
  message: ""
};

function licenseIsValid(value) {
  return value === true || value === "true";
}

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

function loadState() {
  try {
    return mergeState(clone(defaults), JSON.parse(localStorage.getItem(STORAGE_KEY)));
  } catch {
    return clone(defaults);
  }
}

function mergeState(base, saved) {
  if (!saved || typeof saved !== "object") return base;
  const merged = {
    ...base,
    ...saved,
    bases: { ...base.bases, ...(saved.bases || {}) },
    lineupOverlay: { ...base.lineupOverlay, ...(saved.lineupOverlay || {}) },
    lineups: {
      away: mergeLineup(base.lineups.away, saved.lineups && saved.lineups.away),
      home: mergeLineup(base.lineups.home, saved.lineups && saved.lineups.home)
    },
    savedLineups: { ...base.savedLineups, ...(saved.savedLineups || {}) },
    inningRuns: {
      away: Array.isArray(saved.inningRuns && saved.inningRuns.away) ? saved.inningRuns.away : base.inningRuns.away,
      home: Array.isArray(saved.inningRuns && saved.inningRuns.home) ? saved.inningRuns.home : base.inningRuns.home
    },
    pitcherPitchCounts: { ...base.pitcherPitchCounts, ...(saved.pitcherPitchCounts || {}) },
    teams: {
      away: { ...base.teams.away, ...(saved.teams && saved.teams.away) },
      home: { ...base.teams.home, ...(saved.teams && saved.teams.home) }
    }
  };

  delete merged.currentBatter;
  delete merged.batterPitchCounts;
  if (!merged.pitcher) merged.pitcher = base.pitcher;
  if (typeof merged.pitcherPitchCounts[merged.pitcher] !== "number") {
    merged.pitcherPitchCounts[merged.pitcher] = Math.max(0, Number(saved.pitchCount || 0));
  }
  merged.pitchCount = merged.pitcherPitchCounts[merged.pitcher];
  return merged;
}

function mergeLineup(baseLineup, savedLineup) {
  if (!savedLineup || typeof savedLineup !== "object") return baseLineup;
  const hasNewModel = Array.isArray(savedLineup.battingOrder);
  const starters = Array.from({ length: 9 }, () => "");
  const battingOrder = Array.from({ length: 9 }, (_, index) => index + 1);

  if (hasNewModel) {
    for (let index = 0; index < 9; index += 1) {
      starters[index] = Array.isArray(savedLineup.starters) ? savedLineup.starters[index] || "" : "";
      const order = Number(savedLineup.battingOrder[index]);
      battingOrder[index] = order >= 1 && order <= 9 ? order : index + 1;
    }
  } else {
    for (let battingIndex = 0; battingIndex < 9; battingIndex += 1) {
      const positionCode = Array.isArray(savedLineup.positions)
        ? savedLineup.positions[battingIndex]
        : fieldPositions[battingIndex].label.split("-")[1];
      const fieldIndex = fieldPositions.findIndex((position) => position.label.endsWith(`-${positionCode}`));
      const targetIndex = fieldIndex >= 0 ? fieldIndex : battingIndex;
      starters[targetIndex] = Array.isArray(savedLineup.starters) ? savedLineup.starters[battingIndex] || "" : "";
      battingOrder[targetIndex] = battingIndex + 1;
    }
  }

  return {
    starters,
    positions: fieldPositions.map((position) => position.label.split("-")[1]),
    battingOrder,
    dh: {
      name: savedLineup.dh && typeof savedLineup.dh.name === "string" ? savedLineup.dh.name : "",
      replaces: clamp(Number(savedLineup.dh && savedLineup.dh.replaces) || 1, 1, 9)
    },
    reserves: savedLineup.reserves || ""
  };
}

function battingLineup(lineup) {
  const slots = Array.from({ length: 9 }, () => ({ name: "", position: "" }));
  lineup.starters.forEach((player, fieldIndex) => {
    const order = clamp(Number(lineup.battingOrder[fieldIndex]) || fieldIndex + 1, 1, 9);
    slots[order - 1] = {
      name: player || "",
      position: fieldPositions[fieldIndex].label.split("-")[1]
    };
  });
  const replacedIndex = clamp(Number(lineup.dh.replaces) || 1, 1, 9) - 1;
  if (lineup.dh.name.trim()) {
    const order = clamp(Number(lineup.battingOrder[replacedIndex]) || replacedIndex + 1, 1, 9);
    slots[order - 1] = { name: lineup.dh.name, position: "DH" };
  }
  return slots;
}

function setBattingOrder(lineup, fieldIndex, nextOrder) {
  const oldOrder = lineup.battingOrder[fieldIndex];
  const normalizedOrder = clamp(Number(nextOrder) || oldOrder, 1, 9);
  const occupiedIndex = lineup.battingOrder.findIndex((order, index) => {
    return index !== fieldIndex && Number(order) === normalizedOrder;
  });
  if (occupiedIndex >= 0) lineup.battingOrder[occupiedIndex] = oldOrder;
  lineup.battingOrder[fieldIndex] = normalizedOrder;
}

function saveState() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
  if (channel) channel.postMessage(state);
  if (serverMode && !isApplyingRemoteState) {
    fetch("/api/state", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(state)
    }).catch(() => {});
  }
  render();
}

function clamp(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function setText(id, value) {
  const element = document.getElementById(id);
  if (element) element.textContent = value;
}

function setValue(id, value) {
  const element = document.getElementById(id);
  if (element && element.value !== String(value)) element.value = value;
}

function setChecked(id, value) {
  const element = document.getElementById(id);
  if (element) element.checked = Boolean(value);
}

function formatOverlayDate(date = new Date(), language = state.language) {
  const monthsByLanguage = {
    it: ["GEN", "FEB", "MAR", "APR", "MAG", "GIU", "LUG", "AGO", "SET", "OTT", "NOV", "DIC"],
    en: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"],
    es: ["ENE", "FEB", "MAR", "ABR", "MAY", "JUN", "JUL", "AGO", "SEP", "OCT", "NOV", "DIC"]
  };
  const months = monthsByLanguage[language] || monthsByLanguage.it;
  return `${String(date.getDate()).padStart(2, "0")} ${months[date.getMonth()]} ${date.getFullYear()}`;
}

function t(key) {
  const language = translations[state.language] ? state.language : "it";
  return translations[language][key] || translations.it[key] || key;
}

function applyTranslations() {
  const language = translations[state.language] ? state.language : "it";
  document.documentElement.lang = language;
  document.querySelectorAll("[data-i18n]").forEach((element) => {
    element.textContent = t(element.dataset.i18n);
  });
  document.querySelectorAll("[data-i18n-placeholder]").forEach((element) => {
    element.placeholder = t(element.dataset.i18nPlaceholder);
  });
  document.querySelectorAll("[data-language]").forEach((button) => {
    button.classList.toggle("active", button.dataset.language === language);
  });
}

function battingTeam() {
  return state.half === "top" ? "away" : "home";
}

function ensureInningCell(teamKey, inning = state.inning) {
  const index = inning - 1;
  if (typeof state.inningRuns[teamKey][index] !== "number") {
    state.inningRuns[teamKey][index] = 0;
  }
  return index;
}

function changeRuns(teamKey, amount) {
  const team = state.teams[teamKey];
  if (amount > 0) {
    team.score += amount;
    const index = ensureInningCell(teamKey);
    state.inningRuns[teamKey][index] += amount;
    return;
  }

  if (amount < 0 && team.score > 0) {
    const currentIndex = state.inning - 1;
    let targetIndex = currentIndex;
    if (!state.inningRuns[teamKey][targetIndex]) {
      targetIndex = state.inningRuns[teamKey].length - 1;
      while (targetIndex >= 0 && !state.inningRuns[teamKey][targetIndex]) {
        targetIndex -= 1;
      }
    }
    if (targetIndex < 0) return;
    state.inningRuns[teamKey][targetIndex] = Math.max(0, state.inningRuns[teamKey][targetIndex] - 1);
    team.score = Math.max(0, team.score - 1);
  }
}

function changeHits(teamKey, amount) {
  const team = state.teams[teamKey];
  team.hits = Math.max(0, Number(team.hits || 0) + amount);
}

function finalizeHalf(teamKey = battingTeam(), inning = state.inning) {
  ensureInningCell(teamKey, inning);
}

function setHalf(nextHalf) {
  if (nextHalf === state.half) return;
  finalizeHalf();
  state.half = nextHalf;
  if (nextHalf === "top") {
    state.inning = clamp(state.inning + 1, 1, 99);
  }
  resetCount();
}

function normalizeHex(value) {
  const text = String(value || "").trim();
  const withHash = text.startsWith("#") ? text : `#${text}`;
  return /^#[0-9a-fA-F]{6}$/.test(withHash) ? withHash.toUpperCase() : null;
}

function setCurrentPitchCount(value) {
  const pitcher = state.pitcher || defaults.pitcher;
  state.pitcherPitchCounts[pitcher] = Math.max(0, value);
  state.pitchCount = state.pitcherPitchCounts[pitcher];
}

function renderTeam(teamKey) {
  const team = state.teams[teamKey];
  setValue(`${teamKey}Name`, team.name);
  setValue(`${teamKey}Abbr`, team.abbr);
  setValue(`${teamKey}Primary`, team.primary);
  setValue(`${teamKey}PrimaryHex`, team.primary.toUpperCase());
  setValue(`${teamKey}Text`, team.text);
  setValue(`${teamKey}TextHex`, team.text.toUpperCase());
  setText(`${teamKey}ScoreOut`, team.score);
  setText(`${teamKey}HitsOut`, Number(team.hits || 0));
  setText(`${teamKey}HeaderLabel`, team.abbr || (teamKey === "away" ? "AWAY" : "HOME"));
  setText(`${teamKey}NameView`, team.name);
  setText(`${teamKey}AbbrView`, team.abbr);
  setText(`${teamKey}ScoreView`, team.score);

  const card = document.querySelector(`.${teamKey}-card`);
  if (card) {
    card.style.setProperty("--team-bg", team.primary);
    card.style.setProperty("--team-text", team.text);
  }

  const logo = document.getElementById(`${teamKey}LogoView`);
  if (logo) {
    logo.src = team.logo || fallbackLogo(team.abbr, team.primary, team.text);
  }
}

function fallbackLogo(abbr, primary, text) {
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96"><rect width="96" height="96" rx="18" fill="${primary}"/><text x="48" y="58" text-anchor="middle" font-family="Arial,sans-serif" font-size="30" font-weight="900" fill="${text}">${String(abbr || "").slice(0, 3)}</text></svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
}

function render() {
  renderTeam("away");
  renderTeam("home");
  setCurrentPitchCount(state.pitchCount);

  setText("inningOut", state.inning);
  setText("ballsOut", state.balls);
  setText("strikesOut", state.strikes);
  setText("outsOut", state.outs);
  setText("pitchCountOut", state.pitchCount);
  setValue("pitcher", state.pitcher);
  setValue("eventTitle", state.eventTitle);
  setValue("venue", state.venue);
  setChecked("showPitcher", state.showPitcher);

  setText("overlayEvent", state.eventTitle);
  setText("overlayVenue", state.venue);
  setText("lineEvent", state.eventTitle);
  setText("lineVenue", state.venue);
  setText("inningView", state.inning);
  setText("halfArrow", state.half === "top" ? "▲" : "▼");
  setText("ballsView", state.balls);
  setText("strikesView", state.strikes);
  setText("outsView", state.outs);
  setText("pitcherView", state.pitcher || "Pitcher");
  setText("pitchCountView", state.pitchCount);
  setText("overlayDate", formatOverlayDate(new Date(), state.language));

  document.getElementById("topHalf")?.classList.toggle("active", state.half === "top");
  document.getElementById("bottomHalf")?.classList.toggle("active", state.half === "bottom");
  document.getElementById("pitcherStrip")?.classList.toggle("hidden", !state.showPitcher);
  ["first", "second", "third"].forEach((base) => {
    document.querySelector(`[data-base="${base}"]`)?.classList.toggle("active", state.bases[base]);
    document.querySelectorAll(`[data-base-view="${base}"]`).forEach((element) => {
      element.classList.toggle("active", state.bases[base]);
    });
  });
  renderPitcherList();
  renderLineScore();
  renderLineupController();
  renderLineupOverlay();
  renderLineupStatus();
  renderHomeRunCelebration();
  applyTranslations();
  renderBranding();
}

function renderBranding() {
  document.querySelectorAll(".license-brand").forEach((element) => element.remove());
  if (!appLicense.valid || !appLicense.licensee) return;
  const badge = document.createElement("div");
  badge.className = "license-brand";
  badge.textContent = `Licensed to ${appLicense.licensee}`;
  document.body.appendChild(badge);
}

function lineScoreCell(teamKey, inningNumber) {
  const value = state.inningRuns[teamKey][inningNumber - 1];
  return typeof value === "number" ? String(value) : "";
}

function lineScoreLogo(teamKey) {
  const team = state.teams[teamKey];
  return team.logo || fallbackLogo(team.abbr, team.primary, team.text);
}

function renderLineScore() {
  const table = document.getElementById("lineScoreTable");
  if (!table) return;

  const inningCount = Math.max(
    9,
    state.inning,
    state.inningRuns.away.length,
    state.inningRuns.home.length
  );
  table.innerHTML = "";
  table.style.setProperty("--inning-count", inningCount);

  const corner = document.createElement("div");
  corner.className = "line-cell line-corner";
  corner.textContent = "TEAM";
  table.appendChild(corner);

  for (let inning = 1; inning <= inningCount; inning += 1) {
    const header = document.createElement("div");
    header.className = "line-cell line-heading";
    header.textContent = inning;
    table.appendChild(header);
  }

  ["R", "H"].forEach((label) => {
    const header = document.createElement("div");
    header.className = "line-cell line-heading line-total-heading";
    header.textContent = label;
    table.appendChild(header);
  });

  ["away", "home"].forEach((teamKey) => {
    const team = state.teams[teamKey];
    const teamCell = document.createElement("div");
    teamCell.className = "line-cell line-team";
    teamCell.style.setProperty("--team-bg", team.primary);
    teamCell.style.setProperty("--team-text", team.text);

    const logo = document.createElement("img");
    logo.alt = "";
    logo.src = lineScoreLogo(teamKey);

    const name = document.createElement("span");
    name.textContent = team.abbr || team.name;
    teamCell.append(logo, name);
    table.appendChild(teamCell);

    for (let inning = 1; inning <= inningCount; inning += 1) {
      const cell = document.createElement("div");
      cell.className = "line-cell line-inning";
      cell.textContent = lineScoreCell(teamKey, inning);
      table.appendChild(cell);
    }

    const runs = document.createElement("div");
    runs.className = "line-cell line-total";
    runs.textContent = team.score;
    table.appendChild(runs);

    const hits = document.createElement("div");
    hits.className = "line-cell line-total";
    hits.textContent = Number(team.hits || 0);
    table.appendChild(hits);
  });
}

function teamDisplayName(teamKey) {
  const team = state.teams[teamKey];
  return team.abbr || team.name || (teamKey === "away" ? "AWAY" : "HOME");
}

function lineupStorageKey(teamKey) {
  const team = state.teams[teamKey];
  return `${team.abbr || team.name || teamKey}`.trim().toUpperCase();
}

function renderLineupController() {
  ["away", "home"].forEach((teamKey) => {
    const team = state.teams[teamKey];
    setText(`${teamKey}LineupTitle`, teamDisplayName(teamKey));
    const logo = document.getElementById(`${teamKey}LineupLogo`);
    if (logo) logo.src = lineScoreLogo(teamKey);
    setValue(`${teamKey}Reserves`, state.lineups[teamKey].reserves);
    renderLineupInputs(teamKey);
    renderSavedLineupSelect(teamKey);
  });
}

function renderLineupInputs(teamKey) {
  const container = document.getElementById(`${teamKey}LineupInputs`);
  if (!container) return;

  if (!container.dataset.ready) {
    const header = document.createElement("div");
    header.className = "lineup-input-header";
    header.innerHTML = '<span data-i18n="defensivePosition"></span><span data-i18n="playerName"></span><span data-i18n="battingOrReplacement"></span>';
    container.appendChild(header);

    for (let index = 0; index < 9; index += 1) {
      const label = document.createElement("label");
      label.className = "lineup-input-row";
      const number = document.createElement("span");
      number.textContent = fieldPositions[index].label;
      const input = document.createElement("input");
      input.type = "text";
      input.autocomplete = "off";
      input.placeholder = fieldPositions[index].name;
      input.dataset.lineupInput = teamKey;
      input.dataset.lineupIndex = index;
      const battingOrder = document.createElement("select");
      battingOrder.dataset.lineupBattingOrder = teamKey;
      battingOrder.dataset.lineupIndex = index;
      for (let order = 1; order <= 9; order += 1) {
        const option = document.createElement("option");
        option.value = order;
        option.textContent = order;
        battingOrder.appendChild(option);
      }
      label.append(number, input, battingOrder);
      container.appendChild(label);
    }

    const dhRow = document.createElement("label");
    dhRow.className = "lineup-input-row lineup-dh-row";
    const dhLabel = document.createElement("span");
    dhLabel.textContent = "DH";
    const dhInput = document.createElement("input");
    dhInput.type = "text";
    dhInput.autocomplete = "off";
    dhInput.placeholder = "Designated hitter";
    dhInput.dataset.lineupDh = teamKey;
    const replaces = document.createElement("select");
    replaces.dataset.lineupDhReplaces = teamKey;
    fieldPositions.forEach((fieldPosition, index) => {
      const option = document.createElement("option");
      option.value = index + 1;
      option.textContent = fieldPosition.label;
      replaces.appendChild(option);
    });
    dhRow.append(dhLabel, dhInput, replaces);
    container.appendChild(dhRow);
    container.dataset.ready = "true";
  }

  container.querySelectorAll("[data-lineup-input]").forEach((input) => {
    const index = Number(input.dataset.lineupIndex);
    if (input.value !== state.lineups[teamKey].starters[index]) {
      input.value = state.lineups[teamKey].starters[index];
    }
  });
  container.querySelectorAll("[data-lineup-batting-order]").forEach((select) => {
    const index = Number(select.dataset.lineupIndex);
    const value = String(state.lineups[teamKey].battingOrder[index]);
    if (select.value !== value) {
      select.value = value;
    }
  });
  const dhInput = container.querySelector("[data-lineup-dh]");
  if (dhInput && dhInput.value !== state.lineups[teamKey].dh.name) dhInput.value = state.lineups[teamKey].dh.name;
  const replaces = container.querySelector("[data-lineup-dh-replaces]");
  if (replaces) replaces.value = String(state.lineups[teamKey].dh.replaces);
}

function renderSavedLineupSelect(teamKey) {
  const select = document.getElementById(`${teamKey}SavedLineups`);
  if (!select) return;
  const currentValue = select.value;
  select.innerHTML = "";

  const empty = document.createElement("option");
  empty.value = "";
  empty.textContent = t("savedLineups");
  select.appendChild(empty);

  Object.keys(state.savedLineups)
    .sort((a, b) => a.localeCompare(b, "it"))
    .forEach((key) => {
      const option = document.createElement("option");
      option.value = key;
      option.textContent = key;
      select.appendChild(option);
    });

  if (currentValue && state.savedLineups[currentValue]) select.value = currentValue;
}

function renderLineupOverlay() {
  const root = document.getElementById("lineupOverlayRoot");
  if (!root) return;
  root.classList.toggle("hidden", !state.lineupOverlay.visible);
  root.innerHTML = "";
  if (!state.lineupOverlay.visible) return;

  const teamKey = state.lineupOverlay.team === "home" ? "home" : "away";
  const team = state.teams[teamKey];
  const lineup = state.lineups[teamKey];
  root.style.setProperty("--team-bg", team.primary);
  root.style.setProperty("--team-text", team.text);

  const header = document.createElement("div");
  header.className = "lineup-overlay-header";
  const logo = document.createElement("img");
  logo.alt = "";
  logo.src = lineScoreLogo(teamKey);
  const title = document.createElement("div");
  const eyebrow = document.createElement("span");
  eyebrow.textContent = teamKey === "away" ? "AWAY LINEUP" : "HOME LINEUP";
  const name = document.createElement("strong");
  name.textContent = team.name || teamDisplayName(teamKey);
  title.append(eyebrow, name);
  header.append(logo, title);

  const main = document.createElement("div");
  main.className = "lineup-main";
  const field = document.createElement("div");
  field.className = "lineup-field";
  lineup.starters.forEach((player, index) => {
    const card = document.createElement("div");
    card.className = "lineup-player-card";
    const defensivePosition = fieldPositions[index].label.split("-")[1];
    card.dataset.order = index + 1;
    const position = document.createElement("span");
    position.textContent = defensivePosition;
    const playerName = document.createElement("strong");
    playerName.textContent = player || "";
    card.append(position, playerName);
    field.appendChild(card);
  });

  const battingPanel = document.createElement("div");
  battingPanel.className = "batting-lineup";
  const battingTitle = document.createElement("strong");
  battingTitle.className = "batting-lineup-title";
  battingTitle.textContent = t("offensiveLineup");
  battingPanel.appendChild(battingTitle);
  battingLineup(lineup).forEach((player, index) => {
    const row = document.createElement("div");
    row.className = "batting-lineup-row";
    const order = document.createElement("span");
    order.textContent = index + 1;
    const playerName = document.createElement("strong");
    playerName.textContent = player.name;
    const position = document.createElement("b");
    position.textContent = player.position;
    row.append(order, playerName, position);
    battingPanel.appendChild(row);
  });
  main.append(field, battingPanel);

  const reserves = document.createElement("div");
  reserves.className = "lineup-reserves";
  const reservesTitle = document.createElement("span");
  reservesTitle.textContent = t("reserves").toUpperCase();
  const reservesList = document.createElement("div");
  lineup.reserves
    .split("\n")
    .map((item) => item.trim())
    .filter(Boolean)
    .forEach((item) => {
      const player = document.createElement("b");
      player.textContent = item;
      reservesList.appendChild(player);
    });
  reserves.append(reservesTitle, reservesList);

  const credit = document.createElement("div");
  credit.className = "overlay-credit";
  credit.textContent = "www.baseball-overlay.com";

  root.append(header, main, reserves, credit);
}

function renderLineupStatus() {
  const status = document.getElementById("lineupVisibleStatus");
  if (!status) return;
  status.textContent = state.lineupOverlay.visible
    ? `${t("showing")} ${state.lineupOverlay.team === "home" ? "Home" : "Away"}`
    : t("hidden");
}

function resetCount() {
  state.balls = 0;
  state.strikes = 0;
}

function advanceOut() {
  state.outs += 1;
  if (state.outs >= 3) {
    state.outs = 0;
    setHalf(state.half === "top" ? "bottom" : "top");
  }
  resetCount();
}

function recordPitch(type) {
  setCurrentPitchCount(state.pitchCount + 1);

  if (type === "ball") {
    state.balls += 1;
    if (state.balls >= 4) resetCount();
  }

  if (type === "strike") {
    state.strikes += 1;
    if (state.strikes >= 3) advanceOut();
  }

  if (type === "foul" && state.strikes < 2) {
    state.strikes += 1;
  }

  if (type === "inplay") {
    resetCount();
  }

  state.balls = clamp(state.balls, 0, 3);
  state.strikes = clamp(state.strikes, 0, 2);
  saveState();
}

function stepValue(key, amount) {
  if (key === "inning") {
    if (amount > 0) finalizeHalf();
    state.inning = clamp(state.inning + amount, 1, 99);
  }
  if (key === "balls") state.balls = clamp(state.balls + amount, 0, 3);
  if (key === "strikes") state.strikes = clamp(state.strikes + amount, 0, 2);
  if (key === "outs") {
    if (amount > 0) {
      advanceOut();
    } else {
      state.outs = clamp(state.outs + amount, 0, 2);
    }
  }
  if (key === "pitchCount") setCurrentPitchCount(state.pitchCount + amount);
  saveState();
}

function advanceBases() {
  state.bases.third = state.bases.second;
  state.bases.second = state.bases.first;
  state.bases.first = false;
  saveState();
}

function homeRun() {
  const teamKey = battingTeam();
  const occupiedBases = ["first", "second", "third"].filter((base) => state.bases[base]).length;
  const runs = occupiedBases + 1;
  changeRuns(teamKey, runs);
  changeHits(teamKey, 1);
  setCurrentPitchCount(state.pitchCount + 1);
  resetCount();
  state.bases = { first: false, second: false, third: false };
  state.homeRunEvent = {
    id: Date.now(),
    team: teamKey,
    teamAbbr: state.teams[teamKey].abbr,
    createdAt: Date.now()
  };
  saveState();
}

function renderHomeRunCelebration() {
  const celebration = document.getElementById("homeRunCelebration");
  if (!celebration) return;

  const event = state.homeRunEvent;
  const active = event && Date.now() - Number(event.createdAt || 0) < HOME_RUN_DURATION_MS;
  celebration.classList.toggle("hidden", !active);

  if (!active) {
    if (homeRunHideTimer) {
      clearTimeout(homeRunHideTimer);
      homeRunHideTimer = null;
    }
    return;
  }

  setText("homeRunTeamView", event.teamAbbr || state.teams[event.team]?.abbr || "");
  celebration.dataset.eventId = String(event.id || event.createdAt || "");

  if (homeRunHideTimer) clearTimeout(homeRunHideTimer);
  const remaining = Math.max(0, HOME_RUN_DURATION_MS - (Date.now() - Number(event.createdAt || 0)));
  homeRunHideTimer = setTimeout(() => {
    celebration.classList.add("hidden");
  }, remaining);
}

function renderPitcherList() {
  const list = document.getElementById("pitcherList");
  if (!list) return;
  list.innerHTML = "";

  Object.entries(state.pitcherPitchCounts)
    .sort(([a], [b]) => a.localeCompare(b, "it"))
    .forEach(([name, pitches]) => {
      const button = document.createElement("button");
      const label = document.createElement("span");
      const count = document.createElement("strong");
      button.type = "button";
      label.textContent = name;
      count.textContent = `${pitches} ${t("pitches")}`;
      button.append(label, count);
      button.addEventListener("click", () => {
        selectPitcher(name);
      });
      list.appendChild(button);
    });
}

function selectPitcher(name, initialPitches = 0) {
  const cleanName = name.trim();
  if (!cleanName) return;
  if (typeof state.pitcherPitchCounts[cleanName] !== "number") {
    state.pitcherPitchCounts[cleanName] = initialPitches;
  }
  state.pitcher = cleanName;
  state.pitchCount = state.pitcherPitchCounts[cleanName];
  resetCount();
  document.getElementById("pitcherChooser")?.classList.add("hidden");
  setValue("newPitcherName", "");
  saveState();
}

function bindController() {
  document.addEventListener("click", (event) => {
    const button = event.target.closest("button");
    if (!button || button.matches("[data-base]")) return;
    button.classList.remove("command-flash");
    void button.offsetWidth;
    button.classList.add("command-flash");
    window.setTimeout(() => button.classList.remove("command-flash"), 300);
  });

  ["away", "home"].forEach((teamKey) => {
    ["Name", "Abbr", "Primary", "Text"].forEach((field) => {
      const element = document.getElementById(`${teamKey}${field}`);
      if (!element) return;
      element.addEventListener("input", () => {
        state.teams[teamKey][field.toLowerCase()] = element.value;
        saveState();
      });
    });

    ["Primary", "Text"].forEach((field) => {
      const element = document.getElementById(`${teamKey}${field}Hex`);
      if (!element) return;
      element.addEventListener("input", () => {
        const hex = normalizeHex(element.value);
        if (!hex) return;
        state.teams[teamKey][field.toLowerCase()] = hex;
        saveState();
      });
    });

    document.getElementById(`${teamKey}Logo`)?.addEventListener("change", (event) => {
      const file = event.target.files[0];
      if (!file) return;
      const reader = new FileReader();
      reader.addEventListener("load", () => {
        state.teams[teamKey].logo = reader.result;
        saveState();
      });
      reader.readAsDataURL(file);
    });
  });

  document.querySelectorAll("[data-score]").forEach((button) => {
    button.addEventListener("click", () => {
      const [teamKey, amount] = button.dataset.score.split(":");
      changeRuns(teamKey, Number(amount));
      saveState();
    });
  });

  document.querySelectorAll("[data-hit]").forEach((button) => {
    button.addEventListener("click", () => {
      const [teamKey, amount] = button.dataset.hit.split(":");
      changeHits(teamKey, Number(amount));
      saveState();
    });
  });

  document.querySelectorAll("[data-step]").forEach((button) => {
    button.addEventListener("click", () => {
      const [key, amount] = button.dataset.step.split(":");
      stepValue(key, Number(amount));
    });
  });

  document.querySelectorAll("[data-pitch]").forEach((button) => {
    button.addEventListener("click", () => recordPitch(button.dataset.pitch));
  });

  document.getElementById("topHalf")?.addEventListener("click", () => {
    setHalf("top");
    saveState();
  });

  document.getElementById("bottomHalf")?.addEventListener("click", () => {
    setHalf("bottom");
    saveState();
  });

  document.getElementById("newPitcher")?.addEventListener("click", () => {
    document.getElementById("pitcherChooser")?.classList.toggle("hidden");
    renderPitcherList();
  });

  const pitcherInput = document.getElementById("pitcher");
  const openPitcherChooser = () => {
    document.getElementById("pitcherChooser")?.classList.remove("hidden");
    renderPitcherList();
  };
  pitcherInput?.addEventListener("focus", openPitcherChooser);
  pitcherInput?.addEventListener("click", openPitcherChooser);

  document.getElementById("addPitcher")?.addEventListener("click", () => {
    selectPitcher(document.getElementById("newPitcherName")?.value || "");
  });

  document.getElementById("newPitcherName")?.addEventListener("keydown", (event) => {
    if (event.key === "Enter") selectPitcher(event.target.value);
  });

  document.querySelectorAll("[data-base]").forEach((button) => {
    button.addEventListener("click", () => {
      const base = button.dataset.base;
      state.bases[base] = !state.bases[base];
      saveState();
    });
  });

  document.getElementById("advanceBases")?.addEventListener("click", advanceBases);

  document.getElementById("homeRun")?.addEventListener("click", homeRun);

  document.getElementById("clearBases")?.addEventListener("click", () => {
    state.bases = { first: false, second: false, third: false };
    saveState();
  });

  document.getElementById("resetGame")?.addEventListener("click", () => {
    const confirmed = window.confirm(t("resetConfirm"));
    if (!confirmed) return;
    const lineups = clone(state.lineups);
    const savedLineups = clone(state.savedLineups);
    const language = state.language;
    state = clone(defaults);
    state.lineups = lineups;
    state.savedLineups = savedLineups;
    state.language = language;
    state.homeRunEvent = null;
    saveState();
  });

  ["eventTitle", "venue"].forEach((id) => {
    document.getElementById(id)?.addEventListener("input", (event) => {
      state[id] = event.target.value;
      saveState();
    });
  });

  document.getElementById("pitcher")?.addEventListener("change", (event) => {
    const previousCount = state.pitchCount;
    const cleanName = event.target.value.trim();
    if (!cleanName) {
      setValue("pitcher", state.pitcher);
      return;
    }
    if (typeof state.pitcherPitchCounts[cleanName] !== "number") {
      state.pitcherPitchCounts[cleanName] = previousCount;
    }
    selectPitcher(cleanName);
  });

  document.getElementById("showPitcher")?.addEventListener("change", (event) => {
    state.showPitcher = event.target.checked;
    saveState();
  });

  document.querySelectorAll("[data-language]").forEach((button) => {
    button.addEventListener("click", () => {
      state.language = translations[button.dataset.language] ? button.dataset.language : "it";
      saveState();
    });
  });

  document.querySelectorAll("[data-lineup-input]").forEach((input) => {
    input.addEventListener("input", () => {
      const teamKey = input.dataset.lineupInput;
      const index = Number(input.dataset.lineupIndex);
      state.lineups[teamKey].starters[index] = input.value;
      saveState();
    });
  });

  document.querySelectorAll("[data-lineup-batting-order]").forEach((select) => {
    select.addEventListener("change", () => {
      const teamKey = select.dataset.lineupBattingOrder;
      const index = Number(select.dataset.lineupIndex);
      setBattingOrder(state.lineups[teamKey], index, select.value);
      saveState();
    });
  });

  document.querySelectorAll("[data-lineup-dh]").forEach((input) => {
    input.addEventListener("input", () => {
      state.lineups[input.dataset.lineupDh].dh.name = input.value;
      saveState();
    });
  });

  document.querySelectorAll("[data-lineup-dh-replaces]").forEach((select) => {
    select.addEventListener("change", () => {
      state.lineups[select.dataset.lineupDhReplaces].dh.replaces = Number(select.value);
      saveState();
    });
  });

  document.querySelectorAll("[data-lineup-reserves]").forEach((input) => {
    input.addEventListener("input", () => {
      state.lineups[input.dataset.lineupReserves].reserves = input.value;
      saveState();
    });
  });

  document.querySelectorAll("[data-lineup-show]").forEach((button) => {
    button.addEventListener("click", () => {
      const target = button.dataset.lineupShow;
      state.lineupOverlay.visible = target !== "hide";
      if (target !== "hide") state.lineupOverlay.team = target;
      saveState();
    });
  });

  document.querySelectorAll("[data-save-lineup]").forEach((button) => {
    button.addEventListener("click", () => {
      const teamKey = button.dataset.saveLineup;
      const key = lineupStorageKey(teamKey);
      state.savedLineups[key] = {
        teamName: state.teams[teamKey].name,
        abbr: state.teams[teamKey].abbr,
        starters: [...state.lineups[teamKey].starters],
        positions: [...state.lineups[teamKey].positions],
        battingOrder: [...state.lineups[teamKey].battingOrder],
        dh: { ...state.lineups[teamKey].dh },
        reserves: state.lineups[teamKey].reserves
      };
      saveState();
      const select = document.getElementById(`${teamKey}SavedLineups`);
      if (select) select.value = key;
    });
  });

  document.querySelectorAll("[data-load-lineup]").forEach((button) => {
    button.addEventListener("click", () => {
      const teamKey = button.dataset.loadLineup;
      const select = document.getElementById(`${teamKey}SavedLineups`);
      const saved = select && state.savedLineups[select.value];
      if (!saved) return;
      state.lineups[teamKey] = mergeLineup(emptyLineup(), saved);
      saveState();
    });
  });

  document.getElementById("activateLicense")?.addEventListener("click", async () => {
    const payload = {
      licensee: document.getElementById("licensee")?.value || "",
      expires: document.getElementById("expires")?.value || "",
      key: document.getElementById("licenseKey")?.value || "",
      appVersion: "1.2.0"
    };
    const message = document.getElementById("licenseMessage");
    try {
      const response = await fetch("/api/license", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload)
      });
      appLicense = await response.json();
      if (message) message.textContent = appLicense.message;
      if (licenseIsValid(appLicense.valid)) {
        window.location.assign("/controller.html");
      }
    } catch {
      if (message) message.textContent = "Impossibile salvare la licenza.";
    }
  });
}

window.addEventListener("storage", (event) => {
  if (event.key !== STORAGE_KEY || !event.newValue) return;
  state = mergeState(clone(defaults), JSON.parse(event.newValue));
  render();
});

if (channel) {
  channel.addEventListener("message", (event) => {
    state = mergeState(clone(defaults), event.data);
    render();
  });
}

async function connectServerState() {
  if (!serverMode) return;

  await fetchServerState(true);
  window.setInterval(() => {
    fetchServerState(false);
  }, 700);
}

async function fetchServerState(initializeServer) {
  try {
    const response = await fetch("/api/state", { cache: "no-store" });
    const rawState = await response.text();
    const remoteState = rawState ? JSON.parse(rawState) : null;
    if (remoteState) {
      if (rawState === lastRemoteState) return;
      lastRemoteState = rawState;
      isApplyingRemoteState = true;
      state = mergeState(clone(defaults), remoteState);
      localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
      render();
      isApplyingRemoteState = false;
    } else if (initializeServer) {
      saveState();
    }
  } catch {
    isApplyingRemoteState = false;
  }
}

render();
bindController();
render();
fetchLicenseStatus();
connectServerState();

async function fetchLicenseStatus() {
  if (!serverMode) return;
  try {
    const response = await fetch("/api/license", { cache: "no-store" });
    appLicense = await response.json();
    renderBranding();
    if (document.body.classList.contains("license-page") && licenseIsValid(appLicense.valid)) {
      window.location.assign("/controller.html");
    }
  } catch {
    appLicense = { valid: true, licensee: "", expires: "", message: "" };
  }
}
