defmodule BDS.Desktop.ShellLive.OverlayComponents do
  @moduledoc false

  use Phoenix.Component
  use Gettext, backend: BDS.Gettext

  import Ecto.Query
  require Logger

  alias BDS.{I18n, Media, Metadata, Posts, Repo, Slug, Strings}
  alias BDS.Media.Media, as: MediaRecord
  alias BDS.Media.Translation, as: MediaTranslation
  alias BDS.Posts.{Post, PostMedia, Translation}
  alias BDS.Tags.Tag

  # Expected data/DB exceptions for the overlay fallbacks below. Narrowed (not a
  # broad `rescue error ->`) so programmer bugs (MatchError, FunctionClauseError)
  # surface unmodified instead of being swallowed into a fallback value.
  @overlay_rescue [Ecto.NoResultsError, Ecto.QueryError, DBConnection.ConnectionError]

  embed_templates("overlay_html/*")

  def context(assigns, tab_title, tab_subtitle) do
    project_id = assigns.projects.active_project_id
    metadata = project_metadata(project_id)
    current_tab = assigns.current_tab
    page_language = assigns.page_language
    posts = posts(project_id)
    media = media(project_id)

    %{
      current_tab: %{
        type: current_tab.type,
        id: current_tab.id,
        title: tab_title,
        subtitle: tab_subtitle
      },
      current_post_language: source_language(current_tab, metadata),
      current_media_language: source_language(current_tab, metadata),
      posts: posts,
      media: media,
      post_media_ids: post_media_ids(current_tab),
      blog_languages: blog_languages(metadata),
      language_names: language_names(),
      language_flags: language_flags(),
      existing_translations: existing_translations(current_tab),
      ai_title: BDS.Gettext.lgettext(page_language, "ui", "AI Suggestions"),
      insert_link_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Link"),
      insert_media_title: BDS.Gettext.lgettext(page_language, "ui", "Insert Media"),
      language_picker_title: BDS.Gettext.lgettext(page_language, "ui", "Translate"),
      gallery_title: tab_title,
      ai_fields: ai_fields(current_tab, tab_title, tab_subtitle, page_language),
      delete_details: delete_details(current_tab, page_language),
      merge_details: merge_details(project_id, page_language)
    }
  end

  def kind("ai_suggestions"), do: :ai_suggestions
  def kind("insert_link"), do: :insert_link
  def kind("insert_media"), do: :insert_media
  def kind("language_picker"), do: :language_picker
  def kind("confirm_delete"), do: :confirm_delete
  def kind("confirm_merge"), do: :confirm_merge
  def kind("gallery"), do: :gallery
  def kind(_kind), do: nil

  def tab("internal"), do: :internal
  def tab("external"), do: :external
  def tab(_tab), do: :internal

  def markdown_link(text, url), do: "[#{text}](#{url})"

  def project_metadata(nil), do: %{main_language: "en", blog_languages: []}

  def project_metadata(project_id) do
    {:ok, metadata} = Metadata.get_project_metadata(project_id)
    metadata
  rescue
    error in @overlay_rescue ->
      log_overlay_error("project metadata", error)
      %{main_language: "en", blog_languages: []}
  end

  defp posts(nil), do: []

  defp posts(project_id) do
    Repo.all(
      from post in Post,
        where: post.project_id == ^project_id,
        order_by: [desc: post.updated_at, desc: post.created_at],
        select: %{
          id: post.id,
          title: post.title,
          slug: post.slug,
          status: post.status,
          published_at: post.published_at,
          updated_at: post.updated_at,
          language: post.language
        }
    )
    |> Enum.map(fn post ->
      %{
        id: post.id,
        title: post.title || post.slug || post.id,
        status: Atom.to_string(post.status || :draft),
        canonical_url: canonical_post_url(post)
      }
    end)
  end

  defp media(nil), do: []

  defp media(project_id) do
    Repo.all(
      from media in MediaRecord,
        where: media.project_id == ^project_id,
        order_by: [desc: media.updated_at, desc: media.created_at],
        select: %{
          id: media.id,
          title: media.title,
          original_name: media.original_name,
          mime_type: media.mime_type,
          alt: media.alt,
          caption: media.caption
        }
    )
    |> Enum.map(fn media ->
      %{
        id: media.id,
        title: media.title || media.original_name || media.id,
        original_name: media.original_name || media.id,
        is_image: String.starts_with?(to_string(media.mime_type || ""), "image/"),
        thumbnail_url: "/media-thumbnail/#{media.id}",
        image_url: "/media-thumbnail/#{media.id}?size=large",
        alt_text: media.alt || media.caption || media.title
      }
    end)
  end

  defp post_media_ids(%{type: :post, id: post_id}) do
    Repo.all(
      from pm in PostMedia,
        where: pm.post_id == ^post_id,
        order_by: [asc: pm.sort_order, asc: pm.media_id],
        select: pm.media_id
    )
  rescue
    error in @overlay_rescue ->
      log_overlay_error("post media ids for #{post_id}", error)
      []
  end

  defp post_media_ids(_tab), do: []

  defp existing_translations(%{type: :post, id: post_id}) do
    Repo.all(
      from translation in Translation,
        where: translation.translation_for == ^post_id,
        select: {translation.language, translation.status}
    )
    |> Map.new(fn {language, status} -> {language, Atom.to_string(status || :draft)} end)
  rescue
    error in @overlay_rescue ->
      log_overlay_error("post translations for #{post_id}", error)
      %{}
  end

  defp existing_translations(%{type: :media, id: media_id}) do
    Repo.all(
      from translation in MediaTranslation,
        where: translation.translation_for == ^media_id,
        select: {translation.language, "draft"}
    )
    |> Map.new(fn {language, status} -> {language, status} end)
  rescue
    error in @overlay_rescue ->
      log_overlay_error("media translations for #{media_id}", error)
      %{}
  end

  defp existing_translations(_tab), do: %{}

  defp blog_languages(metadata) do
    ([metadata.main_language || "en"] ++
       (metadata.blog_languages || []) ++ Enum.map(I18n.supported_languages(), & &1.code))
    |> Enum.reject(&is_nil/1)
    |> Enum.uniq()
  end

  defp source_language(%{type: :post, id: post_id}, metadata) do
    case Posts.get_post(post_id) do
      %Post{language: language} when is_binary(language) and language != "" -> language
      _other -> metadata.main_language || "en"
    end
  rescue
    error in @overlay_rescue ->
      log_overlay_error("post source language for #{post_id}", error)
      metadata.main_language || "en"
  end

  defp source_language(%{type: :media, id: media_id}, metadata) do
    case Media.get_media(media_id) do
      %MediaRecord{language: language} when is_binary(language) and language != "" -> language
      _other -> metadata.main_language || "en"
    end
  rescue
    error in @overlay_rescue ->
      log_overlay_error("media source language for #{media_id}", error)
      metadata.main_language || "en"
  end

  defp source_language(_tab, metadata), do: metadata.main_language || "en"

  defp language_names do
    I18n.supported_languages()
    |> Enum.into(%{}, fn language -> {language.code, I18n.language_name(language.code)} end)
  end

  defp language_flags do
    I18n.supported_languages()
    |> Enum.into(%{}, fn language -> {language.code, I18n.flag(language.code)} end)
  end

  defp ai_fields(%{type: :post, id: post_id}, title, subtitle, page_language) do
    case Posts.get_post(post_id) do
      %Post{} = post ->
        [
          %{
            key: "title",
            label: BDS.Gettext.lgettext(page_language, "ui", "Title"),
            current_value: post.title || title,
            suggested_value: "",
            locked: false,
            loading: true
          },
          %{
            key: "excerpt",
            label: BDS.Gettext.lgettext(page_language, "ui", "Excerpt"),
            current_value: post.excerpt || subtitle,
            suggested_value: "",
            locked: false,
            loading: true
          },
          %{
            key: "slug",
            label: BDS.Gettext.lgettext(page_language, "ui", "Slug"),
            current_value: post.slug || Slug.slugify(post.title || title),
            suggested_value: "",
            locked: post.status == :published,
            loading: true
          }
        ]

      _other ->
        []
    end
  rescue
    error in @overlay_rescue ->
      log_overlay_error("post AI fields for #{post_id}", error)
      []
  end

  defp ai_fields(%{type: :media, id: media_id}, title, _subtitle, page_language) do
    case Media.get_media(media_id) do
      %MediaRecord{} = media ->
        [
          %{
            key: "title",
            label: BDS.Gettext.lgettext(page_language, "ui", "Title"),
            current_value: media.title || title,
            suggested_value: "",
            locked: false,
            loading: true
          },
          %{
            key: "alt",
            label: BDS.Gettext.lgettext(page_language, "ui", "Alt Text"),
            current_value: media.alt || "",
            suggested_value: "",
            locked: false,
            loading: true
          },
          %{
            key: "caption",
            label: BDS.Gettext.lgettext(page_language, "ui", "Caption"),
            current_value: media.caption || "",
            suggested_value: "",
            locked: false,
            loading: true
          }
        ]

      _other ->
        []
    end
  rescue
    error in @overlay_rescue ->
      log_overlay_error("media AI fields for #{media_id}", error)
      []
  end

  defp ai_fields(_tab, _title, _subtitle, _page_language), do: []

  defp delete_details(%{type: :media, id: media_id}, page_language) do
    entity_name =
      case Media.get_media(media_id) do
        %MediaRecord{} = media -> media.title || media.original_name || media.id
        _other -> media_id
      end

    reference_list =
      Repo.all(
        from post in Post,
          join: pm in PostMedia,
          on: pm.post_id == post.id,
          where: pm.media_id == ^media_id,
          order_by: [asc: pm.sort_order, desc: post.updated_at],
          select: post.title
      )
      |> Enum.map(&(&1 || media_id))

    %{
      title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"),
      entity_name: entity_name,
      entity_type: "media",
      reference_list: reference_list
    }
  rescue
    error in @overlay_rescue ->
      log_overlay_error("delete media details for #{media_id}", error)

      %{
        title: BDS.Gettext.lgettext(page_language, "ui", "Delete Media"),
        entity_name: media_id,
        entity_type: "media",
        reference_list: []
      }
  end

  defp delete_details(%{type: :tags}, page_language) do
    tag_name =
      Repo.one(from tag in Tag, order_by: [asc: tag.name], limit: 1, select: tag.name)
      |> Kernel.||("tag")

    %{
      title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"),
      entity_name: tag_name,
      entity_type: "tag",
      reference_list: []
    }
  rescue
    error in @overlay_rescue ->
      log_overlay_error("delete tag details", error)

      %{
        title: BDS.Gettext.lgettext(page_language, "ui", "Delete Tag"),
        entity_name: "tag",
        entity_type: "tag",
        reference_list: []
      }
  end

  defp delete_details(_tab, page_language) do
    %{
      title: BDS.Gettext.lgettext(page_language, "ui", "Delete"),
      entity_name: "",
      entity_type: "item",
      reference_list: []
    }
  end

  defp merge_details(project_id, page_language) do
    tags =
      Repo.all(
        from tag in Tag,
          where: tag.project_id == ^project_id,
          order_by: [asc: tag.name],
          limit: 3,
          select: tag.name
      )

    target = List.first(tags) || "tag"

    %{
      target: target,
      count: max(length(tags), 1),
      title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"),
      message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.")
    }
  rescue
    error in @overlay_rescue ->
      log_overlay_error("merge tag details for project #{project_id}", error)

      %{
        target: "tag",
        count: 1,
        title: BDS.Gettext.lgettext(page_language, "ui", "Merge Tags"),
        message: BDS.Gettext.lgettext(page_language, "ui", "Cannot be undone.")
      }
  end

  defp canonical_post_url(post) do
    timestamp = post.published_at || post.updated_at || System.system_time(:millisecond)
    date = DateTime.from_unix!(timestamp, :millisecond)
    "/#{date.year}/#{Strings.pad2(date.month)}/#{Strings.pad2(date.day)}/#{post.slug || post.id}"
  end

  defp log_overlay_error(context, error) do
    Logger.error("overlay component fallback for #{context}: #{Exception.message(error)}")
  end
end
