defmodule BerrypodWeb.Admin.Theme.Index do
  use BerrypodWeb, :live_view

  alias Berrypod.{Pages, Settings}
  alias Berrypod.Media
  alias Berrypod.Theme.{CSSGenerator, Presets, PreviewData}
  alias Berrypod.Workers.FaviconGeneratorWorker

  @impl true
  def mount(_params, _session, socket) do
    theme_settings = Settings.get_theme_settings()
    generated_css = CSSGenerator.generate(theme_settings)
    active_preset = Presets.detect_preset(theme_settings)

    preview_data = %{
      products: PreviewData.products(),
      cart_items: PreviewData.cart_items(),
      testimonials: PreviewData.testimonials(),
      categories: PreviewData.categories()
    }

    logo_image = Media.get_logo()
    header_image = Media.get_header()
    icon_image = Media.get_icon()

    socket =
      socket
      |> assign(:theme_settings, theme_settings)
      |> assign(:site_name, Settings.site_name())
      |> assign(:site_description, Settings.site_description())
      |> assign(:generated_css, generated_css)
      |> assign(:preview_page, :home)
      |> assign(:presets_with_descriptions, Presets.all_with_descriptions())
      |> assign(:active_preset, active_preset)
      |> assign(:preview_data, preview_data)
      |> assign(:logo_image, logo_image)
      |> assign(:header_image, header_image)
      |> assign(:icon_image, icon_image)
      |> assign(:customise_open, false)
      |> assign(:sidebar_collapsed, false)
      |> assign(:cart_drawer_open, false)
      |> allow_upload(:logo_upload,
        accept: ~w(.png .jpg .jpeg .webp .svg),
        max_entries: 1,
        max_file_size: 2_000_000,
        auto_upload: true,
        progress: &handle_progress/3
      )
      |> allow_upload(:header_upload,
        accept: ~w(.png .jpg .jpeg .webp),
        max_entries: 1,
        max_file_size: 5_000_000,
        auto_upload: true,
        progress: &handle_progress/3
      )
      |> allow_upload(:icon_upload,
        accept: ~w(.png .jpg .jpeg .webp .svg),
        max_entries: 1,
        max_file_size: 5_000_000,
        auto_upload: true,
        progress: &handle_progress/3
      )

    {:ok, assign(socket, :from_checklist, false)}
  end

  @impl true
  def handle_params(params, _uri, socket) do
    {:noreply, assign(socket, :from_checklist, params["from"] == "checklist")}
  end

  defp handle_progress(:logo_upload, entry, socket) do
    if entry.done? do
      consume_uploaded_entries(socket, :logo_upload, fn %{path: path}, entry ->
        case Media.upload_from_entry(path, entry, "logo") do
          {:ok, image} ->
            Settings.update_theme_settings(%{logo_image_id: image.id})
            {:ok, image}

          {:error, _} = error ->
            error
        end
      end)
      |> case do
        [image | _] ->
          # Trigger favicon generation if using logo as icon
          if socket.assigns.theme_settings.use_logo_as_icon do
            enqueue_favicon_generation(image.id)
          end

          {:noreply, assign(socket, :logo_image, image)}

        _ ->
          {:noreply, socket}
      end
    else
      {:noreply, socket}
    end
  end

  defp handle_progress(:header_upload, entry, socket) do
    if entry.done? do
      consume_uploaded_entries(socket, :header_upload, fn %{path: path}, entry ->
        case Media.upload_from_entry(path, entry, "header") do
          {:ok, image} ->
            Settings.update_theme_settings(%{header_image_id: image.id})
            {:ok, image}

          {:error, _} = error ->
            error
        end
      end)
      |> case do
        [image | _] ->
          {:noreply, assign(socket, :header_image, image)}

        _ ->
          {:noreply, socket}
      end
    else
      {:noreply, socket}
    end
  end

  defp handle_progress(:icon_upload, entry, socket) do
    if entry.done? do
      consume_uploaded_entries(socket, :icon_upload, fn %{path: path}, entry ->
        case Media.upload_from_entry(path, entry, "icon") do
          {:ok, image} ->
            Settings.update_theme_settings(%{icon_image_id: image.id})
            {:ok, image}

          {:error, _} = error ->
            error
        end
      end)
      |> case do
        [image | _] ->
          enqueue_favicon_generation(image.id)
          {:noreply, assign(socket, :icon_image, image)}

        _ ->
          {:noreply, socket}
      end
    else
      {:noreply, socket}
    end
  end

  @impl true
  def handle_event("apply_preset", %{"preset" => preset_name}, socket) do
    preset_atom = String.to_existing_atom(preset_name)

    case Settings.apply_preset(preset_atom) do
      {:ok, theme_settings} ->
        generated_css = CSSGenerator.generate(theme_settings)

        socket =
          socket
          |> assign(:theme_settings, theme_settings)
          |> assign(:generated_css, generated_css)
          |> assign(:active_preset, preset_atom)
          |> put_flash(:info, "Applied #{preset_name} preset")

        {:noreply, socket}

      {:error, _} ->
        {:noreply, put_flash(socket, :error, "Failed to apply preset")}
    end
  end

  @impl true
  def handle_event("change_preview_page", %{"page" => page_name}, socket) do
    page_atom = String.to_existing_atom(page_name)

    socket =
      socket
      |> assign(:preview_page, page_atom)
      |> push_event("scroll-preview-top", %{})

    {:noreply, socket}
  end

  # Settings stored outside the theme JSON
  @standalone_settings ~w(site_name site_description)

  @impl true
  def handle_event("update_setting", %{"field" => field, "setting_value" => value}, socket)
      when field in @standalone_settings do
    Settings.put_setting(field, value, "string")
    {:noreply, assign(socket, String.to_existing_atom(field), value)}
  end

  @impl true
  def handle_event("update_setting", %{"field" => field, "setting_value" => value}, socket) do
    field_atom = String.to_existing_atom(field)
    attrs = %{field_atom => value}

    case Settings.update_theme_settings(attrs) do
      {:ok, theme_settings} ->
        generated_css = CSSGenerator.generate(theme_settings)
        active_preset = Presets.detect_preset(theme_settings)

        socket =
          socket
          |> assign(:theme_settings, theme_settings)
          |> assign(:generated_css, generated_css)
          |> assign(:active_preset, active_preset)

        {:noreply, socket}

      {:error, _} ->
        {:noreply, socket}
    end
  end

  @impl true
  def handle_event("update_setting", %{"field" => field} = params, socket)
      when field in @standalone_settings do
    value = params[field]

    if value do
      Settings.put_setting(field, value, "string")
      {:noreply, assign(socket, String.to_existing_atom(field), value)}
    else
      {:noreply, socket}
    end
  end

  @impl true
  def handle_event("update_setting", %{"field" => field} = params, socket) do
    # For phx-change events from select/input elements, the value comes from the name attribute
    value = params[field] || params["#{field}_text"] || params["value"]

    if value do
      field_atom = String.to_existing_atom(field)
      attrs = %{field_atom => value}

      case Settings.update_theme_settings(attrs) do
        {:ok, theme_settings} ->
          generated_css = CSSGenerator.generate(theme_settings)
          active_preset = Presets.detect_preset(theme_settings)

          socket =
            socket
            |> assign(:theme_settings, theme_settings)
            |> assign(:generated_css, generated_css)
            |> assign(:active_preset, active_preset)

          {:noreply, socket}

        {:error, _} ->
          {:noreply, socket}
      end
    else
      {:noreply, socket}
    end
  end

  @impl true
  def handle_event("update_color", %{"field" => field, "value" => value}, socket) do
    field_atom = String.to_existing_atom(field)
    attrs = %{field_atom => value}

    case Settings.update_theme_settings(attrs) do
      {:ok, theme_settings} ->
        generated_css = CSSGenerator.generate(theme_settings)
        active_preset = Presets.detect_preset(theme_settings)

        socket =
          socket
          |> assign(:theme_settings, theme_settings)
          |> assign(:generated_css, generated_css)
          |> assign(:active_preset, active_preset)

        {:noreply, socket}

      {:error, _} ->
        {:noreply, socket}
    end
  end

  @impl true
  def handle_event("toggle_setting", %{"field" => field}, socket) do
    field_atom = String.to_existing_atom(field)
    current_value = Map.get(socket.assigns.theme_settings, field_atom)
    attrs = %{field_atom => !current_value}

    case Settings.update_theme_settings(attrs) do
      {:ok, theme_settings} ->
        generated_css = CSSGenerator.generate(theme_settings)
        active_preset = Presets.detect_preset(theme_settings)

        # Trigger favicon regeneration when the icon source changes
        if field_atom == :use_logo_as_icon do
          maybe_enqueue_favicon_from_settings(theme_settings, socket.assigns)
        end

        socket =
          socket
          |> assign(:theme_settings, theme_settings)
          |> assign(:generated_css, generated_css)
          |> assign(:active_preset, active_preset)

        {:noreply, socket}

      {:error, _} ->
        {:noreply, socket}
    end
  end

  @impl true
  def handle_event("save_theme", _params, socket) do
    socket = put_flash(socket, :info, "Theme saved successfully")
    {:noreply, socket}
  end

  @impl true
  def handle_event("update_image_alt", %{"image-id" => image_id, "alt" => alt}, socket) do
    case Media.get_image(image_id) do
      nil ->
        {:noreply, socket}

      image ->
        {:ok, updated} = Media.update_image_metadata(image, %{alt: alt})

        # Refresh the relevant assign so the template sees the new alt text
        socket =
          cond do
            socket.assigns.logo_image && socket.assigns.logo_image.id == image_id ->
              assign(socket, :logo_image, updated)

            socket.assigns.header_image && socket.assigns.header_image.id == image_id ->
              assign(socket, :header_image, updated)

            socket.assigns[:icon_image] && socket.assigns.icon_image &&
                socket.assigns.icon_image.id == image_id ->
              assign(socket, :icon_image, updated)

            true ->
              socket
          end

        {:noreply, socket}
    end
  end

  @impl true
  def handle_event("remove_logo", _params, socket) do
    if logo = socket.assigns.logo_image do
      Media.delete_image(logo)
    end

    Settings.update_theme_settings(%{logo_image_id: nil})

    socket =
      socket
      |> assign(:logo_image, nil)
      |> put_flash(:info, "Logo removed")

    {:noreply, socket}
  end

  @impl true
  def handle_event("remove_header", _params, socket) do
    if header = socket.assigns.header_image do
      Media.delete_image(header)
    end

    Settings.update_theme_settings(%{header_image_id: nil})

    socket =
      socket
      |> assign(:header_image, nil)
      |> put_flash(:info, "Header image removed")

    {:noreply, socket}
  end

  @impl true
  def handle_event("remove_icon", _params, socket) do
    if icon = socket.assigns.icon_image do
      Media.delete_image(icon)
    end

    Settings.update_theme_settings(%{icon_image_id: nil})

    socket =
      socket
      |> assign(:icon_image, nil)
      |> put_flash(:info, "Icon removed")

    {:noreply, socket}
  end

  @impl true
  def handle_event("cancel_upload", %{"ref" => ref, "upload" => upload_name}, socket) do
    upload_atom = String.to_existing_atom(upload_name)
    {:noreply, cancel_upload(socket, upload_atom, ref)}
  end

  @impl true
  def handle_event("toggle_customise", _params, socket) do
    {:noreply, assign(socket, :customise_open, !socket.assigns.customise_open)}
  end

  @impl true
  def handle_event("toggle_sidebar", _params, socket) do
    {:noreply, assign(socket, :sidebar_collapsed, !socket.assigns.sidebar_collapsed)}
  end

  @impl true
  def handle_event("open_cart_drawer", _params, socket) do
    {:noreply, assign(socket, :cart_drawer_open, true)}
  end

  @impl true
  def handle_event("close_cart_drawer", _params, socket) do
    {:noreply, assign(socket, :cart_drawer_open, false)}
  end

  @impl true
  def handle_event("noop", _params, socket) do
    {:noreply, socket}
  end

  def error_to_string(:too_large), do: "File is too large"
  def error_to_string(:too_many_files), do: "Too many files"
  def error_to_string(:not_accepted), do: "File type not accepted"
  def error_to_string(err), do: inspect(err)

  defp enqueue_favicon_generation(source_image_id) do
    %{source_image_id: source_image_id}
    |> FaviconGeneratorWorker.new()
    |> Oban.insert()
  end

  defp maybe_enqueue_favicon_from_settings(theme_settings, assigns) do
    source_id =
      if theme_settings.use_logo_as_icon do
        case assigns.logo_image do
          %{id: id} -> id
          _ -> nil
        end
      else
        case assigns.icon_image do
          %{id: id} -> id
          _ -> nil
        end
      end

    if source_id, do: enqueue_favicon_generation(source_id)
  end

  defp preview_assigns(assigns) do
    assign(assigns, %{
      mode: :preview,
      products: assigns.preview_data.products,
      categories: assigns.preview_data.categories,
      cart_items: PreviewData.cart_drawer_items(),
      cart_count: 2,
      cart_subtotal: "£72.00",
      header_nav_items: BerrypodWeb.ThemeHook.default_header_nav(),
      footer_nav_items: BerrypodWeb.ThemeHook.default_footer_nav()
    })
  end

  # Unified preview — loads page definition, applies context, renders via PageRenderer
  attr :page, :atom, required: true
  attr :preview_data, :map, required: true
  attr :theme_settings, :map, required: true
  attr :site_name, :string, required: true
  attr :logo_image, :any, required: true
  attr :header_image, :any, required: true
  attr :cart_drawer_open, :boolean, default: false

  defp preview_page(assigns) do
    slug = to_string(assigns.page)
    page = Pages.get_page(slug)

    assigns =
      assigns
      |> preview_assigns()
      |> assign(:page, page)
      |> preview_page_context(slug)

    extra = Pages.load_block_data(page.blocks, assigns)
    assigns = assign(assigns, extra)

    ~H"<BerrypodWeb.PageRenderer.render_page {assigns} />"
  end

  # Page-context data needed by specific page types in preview mode
  defp preview_page_context(assigns, "pdp") do
    product = List.first(assigns.preview_data.products)
    option_types = Map.get(product, :option_types) || []
    variants = Map.get(product, :variants) || []

    {selected_options, selected_variant} =
      case variants do
        [first | _] -> {first.options, first}
        [] -> {%{}, nil}
      end

    available_options =
      Enum.reduce(option_types, %{}, fn opt, acc ->
        values = Enum.map(opt.values, & &1.title)
        Map.put(acc, opt.name, values)
      end)

    display_price =
      if selected_variant, do: selected_variant.price, else: product.cheapest_price

    assigns
    |> assign(:product, product)
    |> assign(:gallery_images, build_gallery_images(product))
    |> assign(:option_types, option_types)
    |> assign(:selected_options, selected_options)
    |> assign(:available_options, available_options)
    |> assign(:display_price, display_price)
    |> assign(:quantity, 1)
    |> assign(:option_urls, %{})
  end

  defp preview_page_context(assigns, "cart") do
    cart_items = assigns.preview_data.cart_items

    subtotal =
      Enum.reduce(cart_items, 0, fn item, acc ->
        acc + item.product.cheapest_price * item.quantity
      end)

    assigns
    |> assign(:cart_page_items, cart_items)
    |> assign(:cart_page_subtotal, subtotal)
  end

  defp preview_page_context(assigns, "about") do
    assign(assigns, :content_blocks, PreviewData.about_content())
  end

  defp preview_page_context(assigns, "delivery") do
    assign(assigns, :content_blocks, PreviewData.delivery_content())
  end

  defp preview_page_context(assigns, "privacy") do
    assign(assigns, :content_blocks, PreviewData.privacy_content())
  end

  defp preview_page_context(assigns, "terms") do
    assign(assigns, :content_blocks, PreviewData.terms_content())
  end

  defp preview_page_context(assigns, "error") do
    assign(assigns, %{
      error_code: "404",
      error_title: "Page Not Found",
      error_description:
        "Sorry, we couldn't find the page you're looking for. Perhaps you've mistyped the URL or the page has been moved."
    })
  end

  defp preview_page_context(assigns, _slug), do: assigns

  defp build_gallery_images(product) do
    alias Berrypod.Products.ProductImage

    (Map.get(product, :images) || [])
    |> Enum.sort_by(& &1.position)
    |> Enum.map(fn img -> ProductImage.url(img, 1200) end)
    |> Enum.reject(&is_nil/1)
    |> case do
      [] -> []
      urls -> urls
    end
  end
end
