import gleam/int
import gleam/javascript/promise.{type Promise}
import lustre
import lustre/attribute as attr
import lustre/element.{type Element}
import lustre/element/html
import lustre/event
import lustre/effect.{type Effect}

pub fn main() {
  let app = lustre.application(init, update, view)
  let assert Ok(dispatch) = lustre.start(app, "#app", Nil)

  dispatch
}

type Model {
  Model(count: Int, greeting: String, name: String, time: Int)
}

fn init(_) -> #(Model, Effect(Msg)) {
  #(Model(0, "", "", 0), bind_clock())
}

pub type Msg {
  Increment
  Decrement
  Greet
  GotGreeting(String)
  UpdateName(String)
  Tick(Int)
}

fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
  case msg {
    Increment -> #(Model(..model, count: model.count + 1), effect.none())
    Decrement -> #(Model(..model, count: model.count - 1), effect.none())
    Greet -> #(model, get_greeting(model.name))
    GotGreeting(greeting) -> #(
      Model(..model, greeting: greeting),
      effect.none(),
    )
    UpdateName(name) -> #(Model(..model, name: name), effect.none())
    Tick(time) -> #(Model(..model, time: time), effect.none())
  }
}

fn get_greeting(name: String) -> Effect(Msg) {
  effect.from(do_get_greeting(name, _))
}

fn do_get_greeting(name: String, dispatch: fn(Msg) -> Nil) -> Nil {
  greet(name)
  // |> promise.await()
  |> promise.map(fn(response) {
    case response {
      Ok(greeting) -> GotGreeting(greeting)
      Error(err) -> GotGreeting("Error: " <> err)
    }
  })
  |> promise.tap(dispatch)

  Nil
}

fn bind_clock() -> Effect(Msg) {
  effect.from(fn(dispatch) {
    listen_for_tick(fn(time) {
      tick(time)
      |> dispatch
    })

    Nil
  })
}

@external(javascript, "./ffi/commands.js", "greet")
pub fn greet(name: String) -> Promise(Result(String, String))

type UnlistenFn =
  fn() -> Nil

@external(javascript, "./ffi/commands.js", "listenForTick")
pub fn listen_for_tick(handler: fn(Int) -> Nil) -> Promise(UnlistenFn)

pub type Date

@external(javascript, "./ffi/js_extra.js", "from_unix")
pub fn new_date(timestamp: Int) -> Date

@external(javascript, "./ffi/js_extra.js", "date_to_string")
pub fn date_to_string(date: Date) -> String

fn update_name(text: String) -> Msg {
  UpdateName(text)
}

fn tick(time: Int) -> Msg {
  Tick(time)
}

// -- VIEW

fn view(model: Model) -> Element(Msg) {
  let count = int.to_string(model.count)
  let time =
    model.time
    |> new_date
    |> date_to_string

  html.div([], [
    html.h1([], [element.text("Gleam + Vite + Tauri")]),
    html.div([attr.class("field text-center")], [
      html.label([attr.for("greet_name")], [element.text("Name")]),
      element.text(" "),
      html.input([
        attr.type_("text"),
        attr.name("greet_name"),
        event.on_input(update_name),
      ]),
    ]),
    html.p([attr.class("text-center")], [
      element.text(model.greeting <> " " <> count <> " ✨"),
    ]),
    html.div([attr.class("text-center")], [
      html.button([event.on_click(Decrement)], [element.text("-")]),
      html.button([event.on_click(Increment)], [element.text("+")]),
      html.button([event.on_click(Greet)], [element.text("Greet")]),
    ]),
    html.div([attr.class("clock text-center")], [
      element.text("Clock: " <> time),
    ]),
  ])
}