// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable @devtools/no-lit-render-outside-of-view */

import '../../../ui/kit/kit.js';
import '../../../ui/legacy/legacy.js';

import * as i18n from '../../../core/i18n/i18n.js';
import * as Input from '../../../ui/components/input/input.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';

import {findFlexContainerIcon, findGridContainerIcon, type IconInfo} from './CSSPropertyIconResolver.js';
import stylePropertyEditorStyles from './stylePropertyEditor.css.js';

const UIStrings = {
  /**
   * @description Title of the button that selects a flex property.
   * @example {flex-direction} propertyName
   * @example {column} propertyValue
   */
  selectButton: 'Add {propertyName}: {propertyValue}',
  /**
   * @description Title of the button that deselects a flex property.
   * @example {flex-direction} propertyName
   * @example {row} propertyValue
   */
  deselectButton: 'Remove {propertyName}: {propertyValue}',
  /**
   * @description Label for the dense checkbox in the grid-auto-flow editor.
   */
  denseLabel: 'Dense',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/elements/components/StylePropertyEditor.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

const {render, html, Directives} = Lit;

declare global {
  interface HTMLElementEventMap {
    propertyselected: PropertySelectedEvent;
    propertydeselected: PropertyDeselectedEvent;
  }
}

interface FlexEditorData {
  authoredProperties: Map<string, string>;
  computedProperties: Map<string, string>;
}

interface EditableProperty {
  propertyName: string;
  propertyValues: string[];
}

export class PropertySelectedEvent extends Event {
  static readonly eventName = 'propertyselected';
  data: {name: string, value: string};

  constructor(name: string, value: string) {
    super(PropertySelectedEvent.eventName, {});
    this.data = {name, value};
  }
}

export class PropertyDeselectedEvent extends Event {
  static readonly eventName = 'propertydeselected';
  data: {name: string, value: string};

  constructor(name: string, value: string) {
    super(PropertyDeselectedEvent.eventName, {});
    this.data = {name, value};
  }
}

export class StylePropertyEditor extends HTMLElement {
  readonly #shadow = this.attachShadow({mode: 'open'});
  #authoredProperties = new Map<string, string>();
  #computedProperties = new Map<string, string>();
  protected readonly editableProperties: EditableProperty[] = [];

  getEditableProperties(): EditableProperty[] {
    return this.editableProperties;
  }

  set data(data: FlexEditorData) {
    this.#authoredProperties = data.authoredProperties;
    this.#computedProperties = data.computedProperties;
    this.#render();
  }

  #render(): void {
    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    render(html`
      <style>${stylePropertyEditorStyles}</style>
      <style>${Input.checkboxStyles}</style>
      <div class="container">
        ${this.editableProperties.map(prop => this.#renderProperty(prop))}
      </div>
    `, this.#shadow, {
      host: this,
    });
    // clang-format on
  }

  #renderProperty(prop: EditableProperty): Lit.TemplateResult {
    const authoredValue = this.#authoredProperties.get(prop.propertyName);
    const notAuthored = !authoredValue;
    const shownValue = authoredValue || this.#computedProperties.get(prop.propertyName);
    const classes = Directives.classMap({
      'property-value': true,
      'not-authored': notAuthored,
    });

    // Special handling for grid-auto-flow with dense checkbox
    if (prop.propertyName === 'grid-auto-flow') {
      return this.#renderGridAutoFlowProperty(prop, shownValue, classes);
    }

    return html`<div class="row">
      <div class="property">
        <span class="property-name">${prop.propertyName}</span>: <span class=${classes}>${shownValue}</span>
      </div>
      <div class="buttons">
        ${prop.propertyValues.map(value => this.#renderButton(value, prop.propertyName, value === authoredValue))}
      </div>
    </div>`;
  }

  #renderGridAutoFlowProperty(
      prop: EditableProperty, shownValue: string|undefined,
      classes: ReturnType<typeof Directives.classMap>): Lit.TemplateResult {
    const authoredValue = this.#authoredProperties.get(prop.propertyName);

    const isDense = authoredValue === 'dense' || authoredValue === 'row dense' || authoredValue === 'column dense';
    const isRow = authoredValue === 'row' || authoredValue === 'row dense';
    const isColumn = authoredValue === 'column' || authoredValue === 'column dense';

    return html`<div class="row">
      <div class="property">
        <span class="property-name">${prop.propertyName}</span>: <span class=${classes}>${shownValue}</span>
      </div>
      <div class="buttons">
        ${this.#renderButton('row', prop.propertyName, isRow)}
        ${this.#renderButton('column', prop.propertyName, isColumn)}
        <devtools-checkbox
          .checked=${isDense}
          @change=${(e: Event) => this.#onDenseCheckboxChange(e, isRow, isColumn)}
        >
          ${i18nString(UIStrings.denseLabel)}
        </devtools-checkbox>
      </div>
    </div>`;
  }

  #onDenseCheckboxChange(e: Event, isRow: boolean, isColumn: boolean): void {
    const checked = (e.target as HTMLInputElement).checked;
    const propertyName = 'grid-auto-flow';
    const currentValue = this.#authoredProperties.get(propertyName);

    let newValue = '';
    if (isRow) {
      newValue = checked ? 'row dense' : 'row';
    } else if (isColumn) {
      newValue = checked ? 'column dense' : 'column';
    } else {
      newValue = checked ? 'dense' : '';
    }

    if (currentValue) {
      this.dispatchEvent(new PropertyDeselectedEvent(propertyName, currentValue));
    }

    if (newValue) {
      this.dispatchEvent(new PropertySelectedEvent(propertyName, newValue));
    }
  }

  #renderButton(propertyValue: string, propertyName: string, selected = false): Lit.TemplateResult {
    const query = `${propertyName}: ${propertyValue}`;
    const iconInfo = this.findIcon(query, this.#computedProperties);
    if (!iconInfo) {
      throw new Error(`Icon for ${query} is not found`);
    }
    const transform = `transform: rotate(${iconInfo.rotate}deg) scale(${iconInfo.scaleX}, ${iconInfo.scaleY})`;
    const classes = Directives.classMap({
      button: true,
      selected,
    });
    const values = {propertyName, propertyValue};
    const title = selected ? i18nString(UIStrings.deselectButton, values) : i18nString(UIStrings.selectButton, values);
    return html`
      <button title=${title}
              class=${classes}
              jslog=${
        VisualLogging.item().track({click: true, resize: true}).context(`${propertyName}-${propertyValue}`)}
              @click=${() => this.#onButtonClick(propertyName, propertyValue, selected)}>
        <devtools-icon style=${transform} name=${iconInfo.iconName}>
        </devtools-icon>
      </button>
    `;
  }

  #onButtonClick(propertyName: string, propertyValue: string, selected: boolean): void {
    if (propertyName === 'grid-auto-flow') {
      const currentValue = this.#authoredProperties.get(propertyName);
      const isDense = currentValue?.includes('dense') || false;

      if (selected) {
        const newValue = isDense ? 'dense' : '';

        if (currentValue) {
          this.dispatchEvent(new PropertyDeselectedEvent(propertyName, currentValue));
        }

        if (newValue) {
          this.dispatchEvent(new PropertySelectedEvent(propertyName, newValue));
        }
      } else {
        const newValue = isDense ? `${propertyValue} dense` : propertyValue;

        if (currentValue) {
          this.dispatchEvent(new PropertyDeselectedEvent(propertyName, currentValue));
        }

        this.dispatchEvent(new PropertySelectedEvent(propertyName, newValue));
      }
    } else if (selected) {
      this.dispatchEvent(new PropertyDeselectedEvent(propertyName, propertyValue));
    } else {
      this.dispatchEvent(new PropertySelectedEvent(propertyName, propertyValue));
    }
  }

  protected findIcon(_query: string, _computedProperties: Map<string, string>): IconInfo|null {
    throw new Error('Not implemented');
  }
}

export class FlexboxEditor extends StylePropertyEditor {
  readonly jslogContext = 'cssFlexboxEditor';
  protected override readonly editableProperties: EditableProperty[] = FlexboxEditableProperties;

  protected override findIcon(query: string, computedProperties: Map<string, string>): IconInfo|null {
    return findFlexContainerIcon(query, computedProperties);
  }
}

customElements.define('devtools-flexbox-editor', FlexboxEditor);

declare global {
  interface HTMLElementTagNameMap {
    'devtools-flexbox-editor': FlexboxEditor;
  }
}

export class GridEditor extends StylePropertyEditor {
  readonly jslogContext = 'cssGridEditor';
  protected override readonly editableProperties: EditableProperty[] = GridEditableProperties;

  protected override findIcon(query: string, computedProperties: Map<string, string>): IconInfo|null {
    return findGridContainerIcon(query, computedProperties);
  }
}

customElements.define('devtools-grid-editor', GridEditor);

declare global {
  interface HTMLElementTagNameMap {
    'devtools-grid-editor': GridEditor;
  }
}

export class GridLanesEditor extends StylePropertyEditor {
  readonly jslogContext = 'cssGridLanesEditor';
  protected override readonly editableProperties: EditableProperty[] = GridLanesEditableProperties;

  protected override findIcon(query: string, computedProperties: Map<string, string>): IconInfo|null {
    return findGridContainerIcon(query, computedProperties);
  }
}

customElements.define('devtools-grid-lanes-editor', GridLanesEditor);

declare global {
  interface HTMLElementTagNameMap {
    'devtools-grid-lanes-editor': GridLanesEditor;
  }
}

export const FlexboxEditableProperties = [
  {
    propertyName: 'flex-direction',
    propertyValues: [
      'row',
      'column',
      'row-reverse',
      'column-reverse',
    ],
  },
  {
    propertyName: 'flex-wrap',
    propertyValues: [
      'nowrap',
      'wrap',
    ],
  },
  {
    propertyName: 'align-content',
    propertyValues: [
      'center',
      'flex-start',
      'flex-end',
      'space-around',
      'space-between',
      'stretch',
    ],
  },
  {
    propertyName: 'justify-content',
    propertyValues: [
      'center',
      'flex-start',
      'flex-end',
      'space-between',
      'space-around',
      'space-evenly',
    ],
  },
  {
    propertyName: 'align-items',
    propertyValues: [
      'center',
      'flex-start',
      'flex-end',
      'stretch',
      'baseline',
    ],
  },
];

export const GridEditableProperties = [
  {
    propertyName: 'grid-auto-flow',
    propertyValues: [
      'row',
      'column',
    ],
  },
  {
    propertyName: 'align-content',
    propertyValues: [
      'center',
      'start',
      'end',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
    ],
  },
  {
    propertyName: 'justify-content',
    propertyValues: [
      'center',
      'start',
      'end',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
    ],
  },
  {
    propertyName: 'align-items',
    propertyValues: [
      'center',
      'start',
      'end',
      'stretch',
      'baseline',
    ],
  },
  {
    propertyName: 'justify-items',
    propertyValues: [
      'center',
      'start',
      'end',
      'stretch',
    ],
  },
];

export const GridLanesEditableProperties = [
  {
    propertyName: 'align-content',
    propertyValues: [
      'center',
      'start',
      'end',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
    ],
  },
  {
    propertyName: 'justify-content',
    propertyValues: [
      'center',
      'start',
      'end',
      'space-between',
      'space-around',
      'space-evenly',
      'stretch',
    ],
  },
  {
    propertyName: 'align-items',
    propertyValues: [
      'center',
      'start',
      'end',
      'stretch',
    ],
  },
  {
    propertyName: 'justify-items',
    propertyValues: [
      'center',
      'start',
      'end',
      'stretch',
    ],
  },
];
