import {inject, Injectable} from '@angular/core';
import * as THREE from 'three';
import {RendererService} from './renderer.service';

@Injectable({
  providedIn: 'root'
})
export class PlayerModelService {
  private readonly rendererService = inject(RendererService);

  private playerModel!: THREE.Group;
  private skinTexture!: THREE.Texture;
  private characterVisible: boolean = true;
  private textureLoaded = false;

  /**
   * Loads a Minecraft skin texture from a URL
   * @param textureUrl The URL of the skin texture to load
   * @returns A promise that resolves when the texture is loaded
   */
  loadSkinTexture(textureUrl: string): Promise<void> {
    return new Promise((resolve) => {
      const loader = new THREE.TextureLoader();
      loader.load(textureUrl, (texture) => {
        // Set texture parameters
        texture.magFilter = THREE.NearestFilter;
        texture.minFilter = THREE.NearestFilter;
        this.skinTexture = texture;
        this.textureLoaded = true;

        // If the player model already exists, rebuild it with textures
        if (this.playerModel) {
          // Remove old model
          this.rendererService.scene.remove(this.playerModel);
          // Create new model with textures
          this.createPlayerModel();
        }

        resolve();
      });
    });
  }

  /**
   * Creates a player model with Minecraft-style textures and adds it to the scene
   */
  createPlayerModel(): THREE.Group {
    this.playerModel = new THREE.Group();

    if (this.textureLoaded) {
      // Create textured model if texture is loaded
      this.createTexturedPlayerModel();
    } else {
      // Create simple colored model if no texture is loaded
      this.createSimplePlayerModel();
    }

    this.playerModel.renderOrder = 0;
    this.playerModel.position.y = 0.2;
    this.rendererService.scene.add(this.playerModel);
    return this.playerModel;
  }

  public get showCharacter(): boolean {
    return this.characterVisible;
  }

  public set showCharacter(showCharacter: boolean) {
    this.playerModel.visible = showCharacter;
    this.characterVisible = showCharacter;
  }

  /**
   * Creates a simple colored player model (without textures)
   */
  private createSimplePlayerModel(): void {
    // Head
    const headGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
    const headMaterial = new THREE.MeshLambertMaterial({color: 0xffccaa});
    const head = new THREE.Mesh(headGeometry, headMaterial);
    head.position.y = 1.35;
    this.playerModel.add(head);

    // Body
    const bodyGeometry = new THREE.BoxGeometry(0.5, 0.7, 0.25);
    const bodyMaterial = new THREE.MeshLambertMaterial({color: 0x0000ff});
    const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
    body.position.y = 0.75;
    this.playerModel.add(body);

    // Arms
    const armGeometry = new THREE.BoxGeometry(0.2, 0.7, 0.25);
    const armMaterial = new THREE.MeshLambertMaterial({color: 0xffccaa});

    const leftArm = new THREE.Mesh(armGeometry, armMaterial);
    leftArm.position.set(-0.35, 0.75, 0);
    this.playerModel.add(leftArm);

    const rightArm = new THREE.Mesh(armGeometry, armMaterial);
    rightArm.position.set(0.35, 0.75, 0);
    this.playerModel.add(rightArm);

    // Legs
    const legGeometry = new THREE.BoxGeometry(0.25, 0.7, 0.25);
    const legMaterial = new THREE.MeshLambertMaterial({color: 0x000000});

    const leftLeg = new THREE.Mesh(legGeometry, legMaterial);
    leftLeg.position.set(-0.125, 0.15, 0);
    this.playerModel.add(leftLeg);

    const rightLeg = new THREE.Mesh(legGeometry, legMaterial);
    rightLeg.position.set(0.125, 0.15, 0);
    this.playerModel.add(rightLeg);
  }

  /**
   * Creates a textured player model using the Minecraft skin
   */
  private createTexturedPlayerModel(): void {
    // Create the player with properly mapped textures

    // Head - 8x8x8 pixels in the texture
    this.playerModel.add(this.createBoxWithUvMapping(
      0.5, 0.5, 0.5, // width, height, depth
      [
        {x: 8, y: 0, w: 8, h: 8},   // top
        {x: 16, y: 0, w: 8, h: 8},  // bottom
        {x: 16, y: 8, w: 8, h: 8},  // right
        {x: 0, y: 8, w: 8, h: 8},   // left
        {x: 8, y: 8, w: 8, h: 8},   // front
        {x: 24, y: 8, w: 8, h: 8}   // back
      ],
      {x: 0, y: 1.35, z: 0}         // position
    ));

    // Body - 8x12x4 pixels in the texture
    this.playerModel.add(this.createBoxWithUvMapping(
      0.5, 0.7, 0.25, // width, height, depth
      [
        {x: 20, y: 16, w: 8, h: 4},  // top
        {x: 28, y: 16, w: 8, h: 4},  // bottom
        {x: 28, y: 20, w: 4, h: 12}, // right
        {x: 16, y: 20, w: 4, h: 12}, // left
        {x: 20, y: 20, w: 8, h: 12}, // front
        {x: 32, y: 20, w: 8, h: 12}  // back
      ],
      {x: 0, y: 0.75, z: 0}          // position
    ));

    // Left Arm - 4x12x4 pixels in the texture
    this.playerModel.add(this.createBoxWithUvMapping(
      0.2, 0.7, 0.25, // width, height, depth
      [
        {x: 44, y: 16, w: 4, h: 4},  // top
        {x: 48, y: 16, w: 4, h: 4},  // bottom
        {x: 48, y: 20, w: 4, h: 12}, // right
        {x: 40, y: 20, w: 4, h: 12}, // left
        {x: 44, y: 20, w: 4, h: 12}, // front
        {x: 52, y: 20, w: 4, h: 12}  // back
      ],
      {x: -0.35, y: 0.75, z: 0}      // position
    ));

    // Right Arm - 4x12x4 pixels in the texture
    this.playerModel.add(this.createBoxWithUvMapping(
      0.2, 0.7, 0.25, // width, height, depth
      [
        {x: 44, y: 16, w: 4, h: 4},  // top - mirror of left arm
        {x: 48, y: 16, w: 4, h: 4},  // bottom - mirror of left arm
        {x: 40, y: 20, w: 4, h: 12}, // right - mirror of left arm's left
        {x: 48, y: 20, w: 4, h: 12}, // left - mirror of left arm's right
        {x: 44, y: 20, w: 4, h: 12}, // front - same as left arm
        {x: 52, y: 20, w: 4, h: 12}  // back - same as left arm
      ],
      {x: 0.35, y: 0.75, z: 0}       // position
    ));

    // Left Leg - 4x12x4 pixels in the texture
    this.playerModel.add(this.createBoxWithUvMapping(
      0.26, 0.7, 0.26, // width, height, depth
      [
        {x: 4, y: 16, w: 4, h: 4},   // top
        {x: 8, y: 16, w: 4, h: 4},   // bottom
        {x: 8, y: 20, w: 4, h: 12},  // right
        {x: 0, y: 20, w: 4, h: 12},  // left
        {x: 4, y: 20, w: 4, h: 12},  // front
        {x: 12, y: 20, w: 4, h: 12}  // back
      ],
      {x: -0.125, y: 0.15, z: 0}     // position
    ));

    // Right Leg - 4x12x4 pixels in the texture
    this.playerModel.add(this.createBoxWithUvMapping(
      0.26, 0.7, 0.26, // width, height, depth
      [
        {x: 4, y: 16, w: 4, h: 4},   // top - mirror of left leg
        {x: 8, y: 16, w: 4, h: 4},   // bottom - mirror of left leg
        {x: 0, y: 20, w: 4, h: 12},  // right - mirror of left leg's left
        {x: 8, y: 20, w: 4, h: 12},  // left - mirror of left leg's right
        {x: 4, y: 20, w: 4, h: 12},  // front - same as left leg
        {x: 12, y: 20, w: 4, h: 12}  // back - same as left leg
      ],
      {x: 0.125, y: 0.15, z: 0}      // position
    ));
  }

  /**
   * Creates a box with proper UV mapping for a Minecraft character part
   * @param width Width of the box
   * @param height Height of the box
   * @param depth Depth of the box
   * @param uvMapping Array of UV coordinates for each face (top, bottom, right, left, front, back)
   * @param position Position of the box
   * @returns THREE.Mesh with properly mapped textures
   */
  private createBoxWithUvMapping(
    width: number,
    height: number,
    depth: number,
    uvMapping: Array<{ x: number, y: number, w: number, h: number }>,
    position: { x: number, y: number, z: number }
  ): THREE.Mesh {
    const geometry = new THREE.BoxGeometry(width, height, depth);

    // Remap the custom face order to BoxGeometry face order: px, nx, py, ny, pz, nz
    const faceOrder = [2, 3, 0, 1, 4, 5]; // right, left, top, bottom, front, back
    const textureWidth = 64;
    const textureHeight = 64;
    const uv = geometry.attributes['uv'];

    for (let i = 0; i < 6; i++) {
      const face = uvMapping[faceOrder[i]];
      const x1 = face.x / textureWidth;
      const y1 = 1 - face.y / textureHeight;
      const x2 = (face.x + face.w) / textureWidth;
      const y2 = 1 - (face.y + face.h) / textureHeight;

      let uvs: [number, number][] = [
        [x1, y1], // top-left
        [x2, y1], // top-right
        [x1, y2], // bottom-left
        [x2, y2]  // bottom-right
      ];

      const uvOffset = i * 8;
      for (let j = 0; j < 4; j++) {
        uv.array[uvOffset + j * 2] = uvs[j][0];
        uv.array[uvOffset + j * 2 + 1] = uvs[j][1];
      }
    }

    uv.needsUpdate = true;

    const material = new THREE.MeshBasicMaterial({
      map: this.skinTexture,
      transparent: true,
    });

    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(position.x, position.y, position.z);

    return mesh;
  }
}
