import 'dart:io';

import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path;

import '../mpv/player/player.dart';
import '../utils/app_logger.dart';

/// Generates and manages an ambient lighting GLSL shader that fills letterbox/pillarbox
/// bars with a blurred, dimmed version of the video edges.
///
/// Uses video-aspect-override to fill the window (eliminating black bars), then
/// a GLSL shader composites the sharp original video centered at correct aspect
/// over a blurred background.
///
/// The shader uses MPV's built-in `input_size` and `target_size` uniforms to
/// dynamically compute the video rect position. On window resize, only
/// `video-aspect-override` needs updating — no shader regeneration required.
class AmbientLightingService {
  final Player _player;
  String? _shaderPath;
  bool _enabled = false;

  /// Brightness multiplier for the blurred background (0.0-1.0).
  static const double _brightness = 0.5;

  AmbientLightingService(this._player);

  bool get isEnabled => _enabled;
  bool get isSupported => _player.playerType == 'mpv' && !Platform.isIOS;

  /// Enable ambient lighting effect.
  ///
  /// [videoAspect] - the source video's display aspect ratio (width/height).
  /// [outputAspect] - the player widget's aspect ratio (width/height).
  Future<void> enable(double videoAspect, double outputAspect) async {
    if (!isSupported) return;

    try {
      _shaderPath ??= await _writeShaderToTemp(_generateShader());

      appLogger.d('AmbientLightingService: Shader path: $_shaderPath');

      // Set video-aspect-override to fill the entire output area
      await _player.setProperty('video-aspect-override', outputAspect.toString());

      // Append ambient lighting shader
      await _player.command(['change-list', 'glsl-shaders', 'append', _shaderPath!]);

      _enabled = true;

      appLogger.d('AmbientLightingService: Enabled (video=$videoAspect, output=$outputAspect)');
    } catch (e, st) {
      appLogger.w('AmbientLightingService: Failed to enable', error: e, stackTrace: st);
    }
  }

  /// Disable ambient lighting effect and restore normal letterboxing.
  Future<void> disable() async {
    if (!_enabled) return;

    try {
      if (_shaderPath != null) {
        await _player.command(['change-list', 'glsl-shaders', 'remove', _shaderPath!]);
      }

      await _player.setProperty('video-aspect-override', 'no');

      _enabled = false;

      appLogger.d('AmbientLightingService: Disabled');
    } catch (e, st) {
      appLogger.w('AmbientLightingService: Failed to disable', error: e, stackTrace: st);
    }
  }

  /// Re-append the ambient lighting shader to the chain.
  /// Called by ShaderService after it rebuilds the shader chain (clr + append).
  Future<void> reappendShader() async {
    if (!_enabled || _shaderPath == null) return;
    await _player.command(['change-list', 'glsl-shaders', 'append', _shaderPath!]);
  }

  /// Update video-aspect-override when the window resizes.
  /// The shader adapts automatically via dynamic `target_size` uniform.
  void updateOutputAspect(double outputAspect) {
    if (!_enabled) return;
    _player.setProperty('video-aspect-override', outputAspect.toString());
  }

  /// Generate a static multi-pass GLSL shader.
  ///
  /// Uses MPV's built-in `input_size` (video dimensions) and `target_size`
  /// (output dimensions) uniforms which update automatically on resize.
  /// No baked-in aspect constants — fully adaptive.
  ///
  /// Pipeline (all MAIN hooks):
  /// 1. Save original video as ORIGINAL
  /// 2. Downscale to 1/8 as SMALL (center 60% zoom)
  /// 3-5. Three Kawase blur passes at 1/8 -> BLUR8C
  /// 6. Downscale BLUR8C to 1/64 as TINY
  /// 7-8. Two more Kawase blur passes at 1/64 -> GLOW
  /// 9. Composite: video rect -> ORIGINAL, bars -> GLOW
  String _generateShader() {
    final br = _brightness.toStringAsFixed(2);
    final buf = StringBuffer();

    // Pass 1: Save original video
    buf.writeln('//!HOOK MAIN');
    buf.writeln('//!BIND HOOKED');
    buf.writeln('//!SAVE ORIGINAL');
    buf.writeln('//!DESC Ambient Lighting Save');
    buf.writeln('vec4 hook() {');
    buf.writeln('    return HOOKED_tex(HOOKED_pos);');
    buf.writeln('}');
    buf.writeln();

    // Pass 2: Downscale to 1/8 with center zoom.
    buf.writeln('//!HOOK MAIN');
    buf.writeln('//!BIND ORIGINAL');
    buf.writeln('//!SAVE SMALL');
    buf.writeln('//!WIDTH ORIGINAL.w 8 /');
    buf.writeln('//!HEIGHT ORIGINAL.h 8 /');
    buf.writeln('//!DESC Ambient Lighting Downscale');
    buf.writeln('vec4 hook() {');
    buf.writeln('    vec2 uv = ORIGINAL_pos * 0.6 + 0.2;');
    buf.writeln('    return ORIGINAL_tex(uv);');
    buf.writeln('}');
    buf.writeln();

    // Pass 3-5: Three Kawase blur passes at 1/8 resolution.
    const blur8Steps = [
      ('SMALL', 'BLUR8A', '2.0', 'Blur1'),
      ('BLUR8A', 'BLUR8B', '6.0', 'Blur2'),
      ('BLUR8B', 'BLUR8C', '12.0', 'Blur3'),
    ];
    for (final step in blur8Steps) {
      _writeKawasePass(buf, step.$1, step.$2, step.$3, step.$4);
    }

    // Pass 6: Downscale the already-blurred 1/8 texture to 1/64.
    buf.writeln('//!HOOK MAIN');
    buf.writeln('//!BIND BLUR8C');
    buf.writeln('//!SAVE TINY');
    buf.writeln('//!WIDTH BLUR8C.w 8 /');
    buf.writeln('//!HEIGHT BLUR8C.h 8 /');
    buf.writeln('//!DESC Ambient Lighting Downscale2');
    buf.writeln('vec4 hook() {');
    buf.writeln('    return BLUR8C_tex(BLUR8C_pos);');
    buf.writeln('}');
    buf.writeln();

    // Pass 7-8: Two more Kawase blur passes at 1/64 for maximum diffusion.
    const blur64Steps = [('TINY', 'GLOW1', '3.0', 'Blur4'), ('GLOW1', 'GLOW', '6.0', 'Blur5')];
    for (final step in blur64Steps) {
      _writeKawasePass(buf, step.$1, step.$2, step.$3, step.$4);
    }

    // Pass 9: Composite — no //!SAVE so this replaces MAIN.
    // Uses built-in input_size (video coded dims) and target_size (output dims)
    // to dynamically compute where the video rect sits. These auto-update on resize.
    buf.writeln('//!HOOK MAIN');
    buf.writeln('//!BIND ORIGINAL');
    buf.writeln('//!BIND GLOW');
    buf.writeln('//!DESC Ambient Lighting Composite');
    buf.writeln('vec4 hook() {');
    buf.writeln('    float vid_aspect = input_size.x / input_size.y;');
    buf.writeln('    float out_aspect = target_size.x / target_size.y;');
    buf.writeln();
    buf.writeln('    vec2 scale, off;');
    buf.writeln('    if (vid_aspect > out_aspect) {');
    buf.writeln('        float s = out_aspect / vid_aspect;');
    buf.writeln('        scale = vec2(1.0, s);');
    buf.writeln('        off = vec2(0.0, (1.0 - s) * 0.5);');
    buf.writeln('    } else {');
    buf.writeln('        float s = vid_aspect / out_aspect;');
    buf.writeln('        scale = vec2(s, 1.0);');
    buf.writeln('        off = vec2((1.0 - s) * 0.5, 0.0);');
    buf.writeln('    }');
    buf.writeln();
    buf.writeln('    vec2 vid_pos = (ORIGINAL_pos - off) / scale;');
    buf.writeln();
    buf.writeln('    if (all(greaterThanEqual(vid_pos, vec2(0.0))) &&');
    buf.writeln('        all(lessThanEqual(vid_pos, vec2(1.0)))) {');
    buf.writeln('        return ORIGINAL_tex(vid_pos);');
    buf.writeln('    }');
    buf.writeln();
    buf.writeln('    return GLOW_tex(ORIGINAL_pos) * $br;');
    buf.writeln('}');

    return buf.toString();
  }

  void _writeKawasePass(StringBuffer buf, String input, String output, String offset, String desc) {
    buf.writeln('//!HOOK MAIN');
    buf.writeln('//!BIND $input');
    buf.writeln('//!SAVE $output');
    buf.writeln('//!WIDTH $input.w');
    buf.writeln('//!HEIGHT $input.h');
    buf.writeln('//!DESC Ambient Lighting $desc');
    buf.writeln('vec4 hook() {');
    buf.writeln('    vec2 ps = ${input}_pt;');
    buf.writeln('    vec4 s = ${input}_tex(${input}_pos + vec2( $offset,  $offset) * ps)');
    buf.writeln('           + ${input}_tex(${input}_pos + vec2( $offset, -$offset) * ps)');
    buf.writeln('           + ${input}_tex(${input}_pos + vec2(-$offset,  $offset) * ps)');
    buf.writeln('           + ${input}_tex(${input}_pos + vec2(-$offset, -$offset) * ps);');
    buf.writeln('    return s * 0.25;');
    buf.writeln('}');
    buf.writeln();
  }

  /// Write the shader to a temp file and return the path.
  Future<String> _writeShaderToTemp(String shader) async {
    final cacheDir = await getTemporaryDirectory();
    final shaderDir = Directory(path.join(cacheDir.path, 'shaders'));
    if (!shaderDir.existsSync()) {
      shaderDir.createSync(recursive: true);
    }
    final file = File(path.join(shaderDir.path, 'ambient_lighting.glsl'));
    await file.writeAsString(shader);
    return file.path;
  }
}
