import { mdiImagePlus } from "@mdi/js";
import type { TemplateResult } from "lit";
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import type { MediaPickedEvent } from "../data/media-player";
import { fireEvent } from "../common/dom/fire_event";
import { haStyle } from "../resources/styles";
import {
  MEDIA_PREFIX,
  getIdFromUrl,
  createImage,
  generateImageThumbnailUrl,
  getImageData,
} from "../data/image_upload";
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
import { showImageCropperDialog } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
import type { HomeAssistant } from "../types";
import "./ha-button";
import "./ha-file-upload";
import { showMediaBrowserDialog } from "./media-player/show-media-browser-dialog";

@customElement("ha-picture-upload")
export class HaPictureUpload extends LitElement {
  public hass!: HomeAssistant;

  @property() public value: string | null = null;

  @property() public label?: string;

  @property() public secondary?: string;

  @property() public supports?: string;

  @property({ attribute: false }) public currentImageAltText?: string;

  @property({ type: Boolean }) public crop = false;

  @property({ type: Boolean, attribute: "select-media" }) public selectMedia =
    false;

  // This property is set when this component is used inside a media selector.
  // When set, it returns selected media or uploaded files as MediaSelectorValue
  // When unset, it only allows selecting images from image-upload, and returns
  // selected or uploaded images as a string starting with /api/...
  @property({ type: Boolean, attribute: "full-media" }) public fullMedia =
    false;

  @property({ attribute: false }) public contentIdHelper?: string;

  @property({ attribute: false }) public cropOptions?: CropOptions;

  @property({ type: Boolean }) public original = false;

  @property({ type: Number }) public size = 512;

  @state() private _uploading = false;

  public render(): TemplateResult {
    if (!this.value) {
      const secondary =
        this.secondary ||
        (this.selectMedia
          ? html`${this.hass.localize(
              "ui.components.picture-upload.secondary",
              {
                select_media: html`<button
                  class="link"
                  @click=${this._chooseMedia}
                >
                  ${this.hass.localize(
                    "ui.components.picture-upload.select_media"
                  )}
                </button>`,
              }
            )}`
          : undefined);

      return html`
        <ha-file-upload
          .icon=${mdiImagePlus}
          .label=${
            this.label ||
            this.hass.localize("ui.components.picture-upload.label")
          }
          .secondary=${secondary}
          .supports=${
            this.supports ||
            this.hass.localize("ui.components.picture-upload.supported_formats")
          }
          .uploading=${this._uploading}
          @file-picked=${this._handleFilePicked}
          @change=${this._handleFileCleared}
          accept="image/png, image/jpeg, image/gif"
        ></ha-file-upload>
      `;
    }
    return html`<div class="center-vertical">
      <div class="value">
        <img
          .src=${this.value}
          alt=${
            this.currentImageAltText ||
            this.hass.localize("ui.components.picture-upload.current_image_alt")
          }
        />
        <div>
          <ha-button
            appearance="plain"
            size="s"
            variant="danger"
            @click=${this._handleChangeClick}
          >
            ${this.hass.localize("ui.components.picture-upload.clear_picture")}
          </ha-button>
        </div>
      </div>
    </div>`;
  }

  private _handleChangeClick() {
    this.value = null;
    fireEvent(this, "change");
  }

  private async _handleFilePicked(ev) {
    const file = ev.detail.files[0];
    if (this.crop) {
      this._cropFile(file);
    } else {
      this._uploadFile(file);
    }
  }

  private async _handleFileCleared() {
    this.value = null;
  }

  private async _cropFile(file: File, mediaId?: string) {
    if (!["image/png", "image/jpeg", "image/gif"].includes(file.type)) {
      showAlertDialog(this, {
        text: this.hass.localize(
          "ui.components.picture-upload.unsupported_format"
        ),
      });
      return;
    }
    showImageCropperDialog(this, {
      file,
      options: this.cropOptions || {
        round: false,
        aspectRatio: NaN,
      },
      croppedCallback: (croppedFile) => {
        if (mediaId && croppedFile === file) {
          this.value = generateImageThumbnailUrl(
            mediaId,
            this.size,
            this.original
          );
          fireEvent(this, "change");
        } else {
          this._uploadFile(croppedFile);
        }
      },
    });
  }

  private async _uploadFile(file: File) {
    if (!["image/png", "image/jpeg", "image/gif"].includes(file.type)) {
      showAlertDialog(this, {
        text: this.hass.localize(
          "ui.components.picture-upload.unsupported_format"
        ),
      });
      return;
    }
    this._uploading = true;
    try {
      const media = await createImage(this.hass, file);
      if (this.fullMedia) {
        const item = {
          media_content_id: `${MEDIA_PREFIX}/${media.id}`,
          media_content_type: media.content_type,
          title: media.name,
          media_class: "image" as const,
          can_play: true,
          can_expand: false,
          can_search: false,
          thumbnail: generateImageThumbnailUrl(media.id, 256),
        } as const;
        const navigateIds = [
          {},
          { media_content_type: "app", media_content_id: MEDIA_PREFIX },
        ];
        fireEvent(this, "media-picked", {
          item,
          navigateIds,
        });
      } else {
        this.value = generateImageThumbnailUrl(
          media.id,
          this.size,
          this.original
        );
        fireEvent(this, "change");
      }
    } catch (err: any) {
      showAlertDialog(this, {
        text: err.toString(),
      });
    } finally {
      this._uploading = false;
    }
  }

  private _chooseMedia = () => {
    showMediaBrowserDialog(this, {
      action: "pick",
      entityId: "browser",
      accept: ["image/*"],
      navigateIds: this.fullMedia
        ? undefined
        : [
            { media_content_id: undefined, media_content_type: undefined },
            {
              media_content_id: MEDIA_PREFIX,
              media_content_type: "app",
            },
          ],
      minimumNavigateLevel: this.fullMedia ? undefined : 2,
      hideContentType: true,
      contentIdHelper: this.contentIdHelper,
      mediaPickedCallback: async (pickedMedia: MediaPickedEvent) => {
        if (this.fullMedia) {
          fireEvent(this, "media-picked", pickedMedia);
          return;
        }
        const mediaId = getIdFromUrl(pickedMedia.item.media_content_id);
        if (mediaId) {
          if (this.crop) {
            const url = generateImageThumbnailUrl(mediaId, undefined, true);
            let data;
            try {
              data = await getImageData(this.hass, url);
            } catch (err: any) {
              showAlertDialog(this, {
                text: err.toString(),
              });
              return;
            }
            const metadata = {
              type: pickedMedia.item.media_content_type,
            };
            const file = new File([data], pickedMedia.item.title, metadata);
            this._cropFile(file, mediaId);
          } else {
            this.value = generateImageThumbnailUrl(
              mediaId,
              this.size,
              this.original
            );
            fireEvent(this, "change");
          }
        }
      },
    });
  };

  static get styles() {
    return [
      haStyle,
      css`
        :host {
          display: block;
          height: 240px;
        }
        ha-file-upload {
          height: 100%;
        }
        .center-vertical {
          display: flex;
          align-items: center;
          height: 100%;
        }
        .value {
          width: 100%;
          display: flex;
          flex-direction: column;
          align-items: center;
        }
        img {
          max-width: 100%;
          max-height: 200px;
          margin-bottom: 4px;
          border-radius: var(--file-upload-image-border-radius);
          transition: opacity 0.3s;
          opacity: var(--picture-opacity, 1);
        }
        img:hover {
          opacity: 1;
        }
      `,
    ];
  }
}

declare global {
  interface HTMLElementTagNameMap {
    "ha-picture-upload": HaPictureUpload;
  }
}
