// lib/widgets/viewer/visual/raster.dart
import 'dart:math';

import 'package:aves/image_providers/region_provider.dart';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/entry/extensions/images.dart';
import 'package:aves/model/entry/extensions/props.dart';
import 'package:aves/model/settings/enums/entry_background.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/model/viewer/view_state.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/visual/entry_page_view.dart';
import 'package:aves_model/aves_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:latlong2/latlong.dart';
import 'package:aves/remote/remote_http.dart';

// ✅ NEW: Remote origin costante
import 'package:aves/remote/remote_origin.dart';

class RasterImageView extends StatefulWidget {
  final AvesEntry entry;
  final ValueNotifier<ViewState> viewStateNotifier;
  final ImageErrorWidgetBuilder errorBuilder;

  const RasterImageView({
    super.key,
    required this.entry,
    required this.viewStateNotifier,
    required this.errorBuilder,
  });

  @override
  State<RasterImageView> createState() => _RasterImageViewState();
}

class _RasterImageViewState extends State<RasterImageView> {
  late Size _displaySize;
  late bool _useTiles;
  bool _isTilingInitialized = false;
  late int _maxSampleSize;
  late double _tileSide;
  Matrix4? _tileTransform;
  ImageStream? _fullImageStream;
  late ImageStreamListener _fullImageListener;
  final ValueNotifier<bool> _fullImageLoaded = ValueNotifier(false);
  ImageInfo? _fullImageInfo;

  static const int _pixelArtMaxSize = 256; // px
  static const double _tilesByShortestSide = 2;

  AvesEntry get entry => widget.entry;

  ValueNotifier<ViewState> get viewStateNotifier => widget.viewStateNotifier;

  ViewState get viewState => viewStateNotifier.value;

  ImageProvider get thumbnailProvider => entry.bestCachedThumbnail;

  bool get _isRemote => entry.origin == RemoteOrigin.value;

  // === FULL IMAGE: provider sincrono (necessario per il listener)
  ImageProvider get fullImageProvider {
    if (_isRemote) {
      final abs = RemoteHttp.absUrl(entry.remotePath ?? entry.path);
      final hdrs = RemoteHttp.peekHeaders();
      return NetworkImage(abs, headers: hdrs.isEmpty ? null : hdrs);
    }

    if (_useTiles) {
      assert(_isTilingInitialized);
      return entry.getRegion(
        sampleSize: _maxSampleSize,
        region: entry.fullImageRegion,
      );
    } else {
      return entry.fullImage;
    }
  }

  @override
  void initState() {
    super.initState();
    _displaySize = entry.displaySize;
    // REMOTO: disabilita tiling per i remoti
    _useTiles = _isRemote ? false : entry.useTiles;
    _fullImageListener = ImageStreamListener(_onFullImageCompleted);
    if (!_useTiles) _registerFullImage();
  }

  @override
  void didUpdateWidget(covariant RasterImageView oldWidget) {
    super.didUpdateWidget(oldWidget);

    final oldViewState = oldWidget.viewStateNotifier.value;
    final viewState = widget.viewStateNotifier.value;
    if (oldWidget.entry != widget.entry || oldViewState.viewportSize != viewState.viewportSize) {
      _isTilingInitialized = false;
      _fullImageLoaded.value = false;
      _unregisterFullImage();
    }
  }

  @override
  void dispose() {
    _fullImageLoaded.dispose();
    _unregisterFullImage();
    super.dispose();
  }

  void _registerFullImage() {
    _fullImageStream = fullImageProvider.resolve(ImageConfiguration.empty);
    _fullImageStream!.addListener(_fullImageListener);
  }

  void _unregisterFullImage() {
    _fullImageStream?.removeListener(_fullImageListener);
    _fullImageStream = null;
    _fullImageInfo?.dispose();
    _fullImageInfo = null;
  }

  void _onFullImageCompleted(ImageInfo image, bool synchronousCall) {
    // implementer is responsible for disposing the provided `ImageInfo`
    _unregisterFullImage();
    _fullImageInfo = image;
    _fullImageLoaded.value = true;

    // ⚠️ REMOTO: NON aggiornare _displaySize quando arriva il full.
    // Manteniamo quella di entry.displaySize (remoteWidth/remoteHeight),
    // così il Magnifier non ricompone i limiti e non c'è "shrink".
    FullImageLoadedNotification(entry, fullImageProvider).dispatch(context);
  }

  // REMOTO: non applicare mai scala > 1.0 (evita “partenza zoomata”)
  double _effectiveScale(double raw) => _isRemote ? min(raw, 1.0) : raw;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<ViewState>(
      valueListenable: viewStateNotifier,
      builder: (context, viewState, child) {
        final viewportSize = viewState.viewportSize;
        final viewportSized = viewportSize?.isEmpty == false;
        if (viewportSized && _useTiles && !_isTilingInitialized) _initTiling(viewportSize!);

        final magnifierScale = _effectiveScale(viewState.scale!);
        final sized = _displaySize * magnifierScale;

        return SizedBox.fromSize(
          // evita 0×0 che può portare a overflow numerici in fase di layout
          size: Size(max(1, sized.width), max(1, sized.height)),
          child: Stack(
            alignment: Alignment.center,
            children: [
              if (entry.canHaveAlpha && viewportSized) _buildBackground(magnifierScale),
              _buildLoading(), // placeholder (anche remoto)
              if (_useTiles) ..._buildTiles() else _buildFullImage(magnifierScale),
            ],
          ),
        );
      },
    );
  }

  // === FULL IMAGE (widget) ===
  Widget _buildFullImage(double magnifierScale) {
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
    final quality = _qualityForScaleAndSize(
      magnifierScale: magnifierScale,
      sampleSize: 1,
      devicePixelRatio: devicePixelRatio,
    );

    Widget img = Image(
      image: fullImageProvider,
      gaplessPlayback: true,
      errorBuilder: widget.errorBuilder,
      width: (_displaySize * magnifierScale).width,
      fit: BoxFit.contain, // naturale: niente crop
      filterQuality: quality,
    );

    // EXIF solo per locali (per i remoti è neutralizzato in entry.dart)
    if (!_isRemote) {
      if (entry.isFlipped) {
        final rotated = (entry.rotationDegrees ~/ 90) % 2 != 0;
        final w = (rotated ? (_displaySize.height * magnifierScale) : (_displaySize.width * magnifierScale)) / 2.0;
        final h = (rotated ? (_displaySize.width * magnifierScale) : (_displaySize.height * magnifierScale)) / 2.0;
        final flipper = Matrix4.identity()
          ..translateByDouble(w, h, 0, 1)
          ..scaleByDouble(-1.0, 1.0, 1.0, 1.0)
          ..translateByDouble(-w, -h, 0, 1);
        img = Transform(transform: flipper, child: img);
      }
      final quarterTurns = (entry.rotationDegrees ~/ 90) % 4;
      if (quarterTurns != 0) {
        img = RotatedBox(quarterTurns: quarterTurns, child: img);
      }
    }

    return img;
  }

  // === LOADING / PLACEHOLDER ===
  Widget _buildLoading() {
    return ValueListenableBuilder<bool>(
      valueListenable: _fullImageLoaded,
      builder: (context, fullImageLoaded, child) {
        if (fullImageLoaded) return const SizedBox();

        // Per i remoti, mostra thumb remoto (o path) via HTTP + header async
        if (_isRemote) {
          final rel = entry.remoteThumb2 ?? entry.remoteThumb1 ?? entry.remotePath ?? entry.path;
          if (rel != null && rel.isNotEmpty) {
            final url = RemoteHttp.absUrl(rel);
            return AspectRatio(
              aspectRatio: entry.displayAspectRatio,
              child: FutureBuilder<Map<String, String>>(
                future: RemoteHttp.headers(),
                builder: (context, snap) {
                  if (snap.connectionState != ConnectionState.done) {
                    return const ColoredBox(color: Colors.black12);
                  }
                  final hdrs = snap.data ?? const {};
                  return Image.network(
                    url,
                    fit: BoxFit.contain, // evita “fill” (effetto zoomato) in loading
                    headers: hdrs.isEmpty ? null : hdrs,
                  );
                },
              ),
            );
          }
        }

        // Default: usa il thumbnail “migliore” in cache locale
        return Center(
          child: AspectRatio(
            // mantieni l'aspect ratio originale
            aspectRatio: entry.displayAspectRatio,
            child: Image(
              image: thumbnailProvider,
              fit: BoxFit.contain,
            ),
          ),
        );
      },
    );
  }

  void _initTiling(Size viewportSize) {
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
    _tileSide = viewportSize.shortestSide * devicePixelRatio / _tilesByShortestSide;
    // scale for initial state `contained`
    final containedScale = min(viewportSize.width / _displaySize.width, viewportSize.height / _displaySize.height);
    _maxSampleSize = ExtraAvesEntryImages.sampleSizeForScale(
      magnifierScale: containedScale,
      devicePixelRatio: devicePixelRatio,
    );

    final rotationDegrees = entry.rotationDegrees;
    final isFlipped = entry.isFlipped;
    _tileTransform = null;
    if (rotationDegrees != 0 || isFlipped) {
      _tileTransform = Matrix4.identity()
        ..translateByDouble(entry.width / 2.0, entry.height / 2.0, 0, 1)
        ..scaleByDouble(isFlipped ? -1.0 : 1.0, 1.0, 1.0, 1.0)
        ..rotateZ(-degToRadian(rotationDegrees.toDouble()))
        ..translateByDouble(-_displaySize.width / 2.0, -_displaySize.height / 2.0, 0, 1);
    }
    _isTilingInitialized = true;
    _registerFullImage();
  }

  Widget _buildBackground(double magnifierScale) {
    final viewportSize = viewState.viewportSize!;
    final viewSize = _displaySize * magnifierScale;
    final decorationOffset = ((viewSize - viewportSize) as Offset) / 2 - viewState.position;
    // deflate come “quick way” per evitare bleed
    final decorationSize = (applyBoxFit(BoxFit.none, viewSize, viewportSize).source - const Offset(.5, .5)) as Size;

    Widget child;
    final background = settings.imageBackground;
    if (background == EntryBackground.checkered) {
      final side = viewportSize.shortestSide;
      final checkSize = side / ((side / EntryPageView.decorationCheckSize).round());
      final offset = ((decorationSize - viewportSize) as Offset) / 2;
      child = ValueListenableBuilder<bool>(
        valueListenable: _fullImageLoaded,
        builder: (context, fullImageLoaded, child) {
          if (!fullImageLoaded) return const SizedBox();

          return CustomPaint(
            painter: CheckeredPainter(
              checkSize: checkSize,
              offset: offset,
            ),
          );
        },
      );
    } else {
      child = DecoratedBox(
        decoration: BoxDecoration(
          color: background.color,
        ),
      );
    }
    return Positioned(
      left: decorationOffset.dx >= 0 ? decorationOffset.dx : null,
      top: decorationOffset.dy >= 0 ? decorationOffset.dy : null,
      width: decorationSize.width,
      height: decorationSize.height,
      child: child,
    );
  }

  List<Widget> _buildTiles() {
    if (!_isTilingInitialized) return [];

    final displayWidth = _displaySize.width.round();
    final displayHeight = _displaySize.height.round();
    final viewRect = _getViewRect(displayWidth, displayHeight);
    final magnifierScale = viewState.scale!;
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;

    // per la sample size massima (scala iniziale), l’intera immagine è in view
    final fullImageRegionTile = _RegionTile(
      entry: entry,
      tileRect: Rect.fromLTWH(0, 0, displayWidth * magnifierScale, displayHeight * magnifierScale),
      regionRect: entry.fullImageRegion,
      sampleSize: _maxSampleSize,
      quality: _qualityForScaleAndSize(
        magnifierScale: magnifierScale,
        sampleSize: _maxSampleSize,
        devicePixelRatio: devicePixelRatio,
      ),
    );
    final tiles = [fullImageRegionTile];

    final minSampleSize = min(
      ExtraAvesEntryImages.sampleSizeForScale(
        magnifierScale: magnifierScale,
        devicePixelRatio: devicePixelRatio,
      ),
      _maxSampleSize,
    );

    int nextSampleSize(int sampleSize) => (sampleSize / 2).floor();
    for (var sampleSize = nextSampleSize(_maxSampleSize); sampleSize >= minSampleSize; sampleSize = nextSampleSize(sampleSize)) {
      final regionSide = (_tileSide * sampleSize).round();
      for (var x = 0; x < displayWidth; x += regionSide) {
        for (var y = 0; y < displayHeight; y += regionSide) {
          final rects = _getTileRects(
            x: x,
            y: y,
            regionSide: regionSide,
            displayWidth: displayWidth,
            displayHeight: displayHeight,
            scale: magnifierScale,
            viewRect: viewRect,
          );
          if (rects != null) {
            final (tileRect, regionRect) = rects;
            tiles.add(
              _RegionTile(
                entry: entry,
                tileRect: tileRect,
                regionRect: regionRect,
                sampleSize: sampleSize,
                quality: _qualityForScaleAndSize(
                  magnifierScale: magnifierScale,
                  sampleSize: sampleSize,
                  devicePixelRatio: devicePixelRatio,
                ),
              ),
            );
          }
        }
      }
    }
    return tiles;
  }

  Rect _getViewRect(int displayWidth, int displayHeight) {
    final scale = viewState.scale!;
    final centerOffset = viewState.position;
    final viewportSize = viewState.viewportSize!;
    final viewOrigin = Offset(
      ((displayWidth * scale - viewportSize.width) / 2 - centerOffset.dx),
      ((displayHeight * scale - viewportSize.height) / 2 - centerOffset.dy),
    );
    return viewOrigin & viewportSize;
  }

  (Rect tileRect, Rectangle<num> regionRect)? _getTileRects({
    required int x,
    required int y,
    required int regionSide,
    required int displayWidth,
    required int displayHeight,
    required double scale,
    required Rect viewRect,
  }) {
    final nextX = x + regionSide;
    final nextY = y + regionSide;
    final thisRegionWidth = regionSide - (nextX >= displayWidth ? nextX - displayWidth : 0);
    final thisRegionHeight = regionSide - (nextY >= displayHeight ? nextY - displayHeight : 0);
    final tileRect = Rect.fromLTWH(x * scale, y * scale, thisRegionWidth * scale, thisRegionHeight * scale);

    // only build visible tiles
    if (!viewRect.overlaps(tileRect)) return null;

    Rectangle<num> regionRect;
    if (_tileTransform != null) {
      // apply EXIF orientation
      final regionRectDouble = Rect.fromLTWH(x.toDouble(), y.toDouble(), thisRegionWidth.toDouble(), thisRegionHeight.toDouble());
      final tl = MatrixUtils.transformPoint(_tileTransform!, regionRectDouble.topLeft);
      final br = MatrixUtils.transformPoint(_tileTransform!, regionRectDouble.bottomRight);
      regionRect = Rectangle<double>.fromPoints(
        Point<double>(tl.dx, tl.dy),
        Point<double>(br.dx, br.dy),
      );
    } else {
      regionRect = Rectangle<num>(x, y, thisRegionWidth, thisRegionHeight);
    }
    return (tileRect, regionRect);
  }

  // follow recommended thresholds from `FilterQuality` documentation
  static FilterQuality _qualityForScale({
    required double magnifierScale,
    required int sampleSize,
    required double devicePixelRatio,
  }) {
    final entryScale = magnifierScale * devicePixelRatio;
    final renderingScale = entryScale * sampleSize;
    if (renderingScale > 1) {
      return renderingScale > 10 ? FilterQuality.high : FilterQuality.medium;
    } else {
      return renderingScale < .5 ? FilterQuality.medium : FilterQuality.high;
    }
  }

  // usually follow recommendations, except for small images
  // (like icons, pixel art, etc.) for which the "nearest neighbor" algorithm is used
  FilterQuality _qualityForScaleAndSize({
    required double magnifierScale,
    required int sampleSize,
    required double devicePixelRatio,
  }) {
    if (_displaySize.longestSide < _pixelArtMaxSize) {
      return FilterQuality.none;
    }

    return _qualityForScale(
      magnifierScale: magnifierScale,
      sampleSize: sampleSize,
      devicePixelRatio: devicePixelRatio,
    );
  }
}

class _RegionTile extends StatefulWidget {
  final AvesEntry entry;

  // `tileRect` uses Flutter view coordinates
  // `regionRect` uses the raw image pixel coordinates
  final Rect tileRect;
  final Rectangle<num> regionRect;
  final int sampleSize;
  final FilterQuality quality;

  const _RegionTile({
    required this.entry,
    required this.tileRect,
    required this.regionRect,
    required this.sampleSize,
    required this.quality,
  });

  @override
  State<_RegionTile> createState() => _RegionTileState();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(IntProperty('id', entry.id));
    properties.add(IntProperty('contentId', entry.contentId));
    properties.add(DiagnosticsProperty<Rect>('tileRect', tileRect));
    properties.add(DiagnosticsProperty<Rectangle<num>>('regionRect', regionRect));
    properties.add(IntProperty('sampleSize', sampleSize));
  }
}

class _RegionTileState extends State<_RegionTile> {
  late RegionProvider _provider;

  AvesEntry get entry => widget.entry;

  @override
  void initState() {
    super.initState();
    _registerWidget(widget);
  }

  @override
  void didUpdateWidget(covariant _RegionTile oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.entry != widget.entry ||
        oldWidget.tileRect != widget.tileRect ||
        oldWidget.sampleSize != widget.sampleSize ||
        oldWidget.sampleSize != widget.sampleSize) {
      _unregisterWidget(oldWidget);
      _registerWidget(widget);
    }
  }

  @override
  void dispose() {
    _unregisterWidget(widget);
    super.dispose();
  }

  void _registerWidget(_RegionTile widget) {
    _initProvider();
  }

  void _unregisterWidget(_RegionTile widget) {
    _pauseProvider();
  }

  void _initProvider() {
    _provider = entry.getRegion(
      sampleSize: widget.sampleSize,
      region: widget.regionRect,
    );
  }

  void _pauseProvider() => _provider.pause();

  @override
  Widget build(BuildContext context) {
    final tileRect = widget.tileRect;

    Widget child = Image(
      image: _provider,
      width: tileRect.width,
      height: tileRect.height,
      fit: BoxFit.fill,
      filterQuality: widget.quality,
    );

    // apply EXIF orientation (tile path locale)
    final quarterTurns = entry.rotationDegrees ~/ 90;
    if (entry.isFlipped) {
      final rotated = quarterTurns % 2 != 0;
      final w = (rotated ? tileRect.height : tileRect.width) / 2.0;
      final h = (rotated ? tileRect.width : tileRect.height) / 2.0;
      final flipper = Matrix4.identity()
        ..translateByDouble(w, h, 0, 1)
        ..scaleByDouble(-1.0, 1.0, 1.0, 1.0)
        ..translateByDouble(-w, -h, 0, 1);
      child = Transform(
        transform: flipper,
        child: child,
      );
    }
    if (quarterTurns != 0) {
      child = RotatedBox(
        quarterTurns: quarterTurns,
        child: child,
      );
    }

    if (settings.debugShowViewerTiles) {
      final regionRect = widget.regionRect;
      child = Stack(
        children: [
          Positioned.fill(child: child),
          Text(
            '\ntile=(${tileRect.left.round()}, ${tileRect.top.round()}) ${tileRect.width.round()} x ${tileRect.height.round()}'
            '\nregion=(${regionRect.left.round()},{${regionRect.top.round()}) ${regionRect.width.round()} x ${regionRect.height.round()}'
            '\nsampling=${widget.sampleSize} quality=${widget.quality.name}',
            style: const TextStyle(backgroundColor: Colors.black87),
          ),
          Positioned.fill(
            child: DecoratedBox(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.red, width: 1),
              ),
            ),
          ),
        ],
      );
    }

    return Positioned.fromRect(
      rect: tileRect,
      child: child,
    );
  }
}
