import { html, LitElement, nothing, type TemplateResult } from 'lit'
import app from '../store/app.ts'
import {
  fetchTileManifest,
  parentLabel,
  type TileManifestEntry,
} from '../utils/tile-manifest.ts'
import { downloadAndSavePMTiles } from '../utils/fs.ts'
import { setMapNav } from '../utils/nav.ts'

const REGIONAL_ZOOM = 12
const MAX_MATCHES = 3

type LocationStatus = 'idle' | 'requesting' | 'granted' | 'denied'
type DownloadStatus = 'idle' | 'downloading' | 'done' | 'error'

export class MWelcome extends LitElement {
  #locationStatus: LocationStatus = 'idle'
  #downloadStatuses: Record<string, DownloadStatus> = {}
  #matchingTiles: TileManifestEntry[] = []
  #manifest: TileManifestEntry[] = []
  #coords: { lat: number; lng: number } | null = null
  #dismissed = false

  protected override createRenderRoot() {
    return this
  }

  override connectedCallback() {
    super.connectedCallback()
    app.addEventListener(this.#onAppUpdate)
    globalThis.addEventListener('hashchange', this.#onAppUpdate)
  }

  override disconnectedCallback() {
    super.disconnectedCallback()
    app.removeEventListener(this.#onAppUpdate)
    globalThis.removeEventListener('hashchange', this.#onAppUpdate)
  }

  #onAppUpdate = () => this.requestUpdate()

  /**
   * Only greet on the map itself. Someone deep-linked into a shared list or
   * bookmark came to look at that, not to be asked for their location — the
   * welcome waits until they visit the home map.
   */
  get #onHome(): boolean {
    const path = globalThis.location.hash.replace(/^#!/, '')
    return path === '' || path === '/'
  }

  get #open(): boolean {
    return this.#onHome && app.preferencesLoaded && !app.hasSeenWelcome &&
      !this.#dismissed
  }

  #requestLocation = () => {
    if (!('geolocation' in navigator)) {
      this.#locationStatus = 'denied'
      this.requestUpdate()
      return
    }
    this.#locationStatus = 'requesting'
    this.requestUpdate()

    navigator.geolocation.getCurrentPosition(
      async (pos) => {
        // Only once permission is actually granted — set eagerly, a denial
        // would leave the app re-prompting on every load.
        app.setTrackOnInitialLoad(true)
        const { latitude: lat, longitude: lng } = pos.coords
        this.#coords = { lat, lng }
        const manifest = await fetchTileManifest()
        this.#manifest = manifest
        const matches = manifest.filter((t) =>
          t.filename && t.bounds &&
          lng >= t.bounds[0] && lat >= t.bounds[1] &&
          lng <= t.bounds[2] && lat <= t.bounds[3]
        )
        // Sort by bounding-box area (smallest first = most specific region).
        matches.sort((a, b) => {
          const [aw, as, ae, an] = a.bounds!
          const [bw, bs, be, bn] = b.bounds!
          return (ae - aw) * (an - as) - (be - bw) * (bn - bs)
        })
        this.#matchingTiles = matches.slice(0, MAX_MATCHES)
        this.#locationStatus = 'granted'
        this.requestUpdate()
      },
      () => {
        this.#locationStatus = 'denied'
        this.requestUpdate()
      },
      { enableHighAccuracy: false, timeout: 10000 },
    )
  }

  #downloadRegion = async (tile: TileManifestEntry) => {
    if (!tile.filename) return
    this.#setStatus(tile.id, 'downloading')
    try {
      await downloadAndSavePMTiles(
        `/static/tiles/${tile.filename}`,
        tile.filename,
      )
      this.#setStatus(tile.id, 'done')
      globalThis.dispatchEvent(new CustomEvent('pmtiles-updated'))
      if (this.#coords) {
        setMapNav({
          lat: this.#coords.lat,
          lng: this.#coords.lng,
          zoom: REGIONAL_ZOOM,
        })
      }
    } catch {
      this.#setStatus(tile.id, 'error')
    }
  }

  #setStatus(id: string, status: DownloadStatus): void {
    this.#downloadStatuses = { ...this.#downloadStatuses, [id]: status }
    this.requestUpdate()
  }

  #dismiss = () => {
    this.#dismissed = true
    this.requestUpdate()
    app.setHasSeenWelcome(true)
  }

  #tileDisplayLabel(tile: TileManifestEntry): string {
    const parent = parentLabel(tile, this.#manifest)
    return parent ? `${tile.label} (${parent})` : tile.label
  }

  override render(): TemplateResult {
    return html`
      <ui-dialog ?open="${this.#open}" @dismiss="${this.#dismiss}">
        <dialog>
          <article class="welcome-dialog">
            <h2 class="welcome-title">Welcome to MapsApp</h2>
            <p class="welcome-intro">
              MapsApp is an offline-capable world map.
            </p>

            <section class="welcome-section">
              <h3>Use your location (Optional)</h3>
              <p class="welcome-muted">Your location stays on your device.</p>
              ${this.#renderLocationAction()}
            </section>

            <section class="welcome-section">
              <h3>Detailed regional maps</h3>
              <p class="welcome-muted">
                The world map is low detail. Download detailed regions for
                street-level maps and for offline use.
              </p>
              ${this.#matchingTiles.length > 0
                ? html`
                  <ul class="welcome-tile-list">
                    ${this.#matchingTiles.map((tile) =>
                      this.#renderTileRow(tile)
                    )}
                  </ul>
                `
                : nothing}
              <a
                href="#!/settings/downloads"
                class="welcome-link"
                @click="${this.#dismiss}"
              >
                Browse all regions →
              </a>
            </section>

            <div class="welcome-actions">
              <button @click="${this.#dismiss}">Get started</button>
            </div>
          </article>
        </dialog>
      </ui-dialog>
    `
  }

  #renderLocationAction(): TemplateResult {
    if (this.#locationStatus === 'requesting') {
      return html`
        <ui-spinner></ui-spinner>
      `
    }
    if (this.#locationStatus === 'granted') {
      const count = this.#matchingTiles.length
      if (count === 0) {
        return html`
          <p class="welcome-muted">
            Location detected, but no matching regional map was found.
          </p>
        `
      }
      return html`
        <p class="welcome-done">
          ✓ Detected ${count} matching region${count === 1 ? '' : 's'}
        </p>
      `
    }
    if (this.#locationStatus === 'denied') {
      return html`
        <p class="welcome-error">
          Location unavailable. You can still choose regions manually.
        </p>
      `
    }
    return html`
      <button class="action" @click="${this.#requestLocation}">
        Allow location
      </button>
    `
  }

  #renderTileRow(tile: TileManifestEntry): TemplateResult {
    const status = this.#downloadStatuses[tile.id] ?? 'idle'
    return html`
      <li class="welcome-tile-row">
        <span class="welcome-tile-label">
          ${this.#tileDisplayLabel(tile)}
        </span>
        ${status === 'done'
          ? html`
            <span class="welcome-done">✓ Downloaded</span>
          `
          : status === 'downloading'
          ? html`
            <ui-spinner></ui-spinner>
          `
          : html`
            <button
              class="action action--compact welcome-tile-action"
              @click="${() => this.#downloadRegion(tile)}"
            >
              ${status === 'error' ? 'Retry' : 'Download'}
            </button>
          `}
      </li>
    `
  }
}

customElements.define('m-welcome', MWelcome)
