// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

/// This sample app demonstrates how to provide a codec for complex extra data.
void main() => runApp(const MyApp());

/// The router configuration.
final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/',
      builder: (BuildContext context, GoRouterState state) =>
          const HomeScreen(),
    ),
  ],
  extraCodec: const MyExtraCodec(),
);

/// The main app.
class MyApp extends StatelessWidget {
  /// Constructs a [MyApp]
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,
    );
  }
}

/// The home screen.
class HomeScreen extends StatelessWidget {
  /// Constructs a [HomeScreen].
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home Screen')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
                "If running in web, use the browser's backward and forward button to test extra codec after setting extra several times."),
            Text(
                'The extra for this page is: ${GoRouterState.of(context).extra}'),
            ElevatedButton(
              onPressed: () => context.go('/', extra: ComplexData1('data')),
              child: const Text('Set extra to ComplexData1'),
            ),
            ElevatedButton(
              onPressed: () => context.go('/', extra: ComplexData2('data')),
              child: const Text('Set extra to ComplexData2'),
            ),
          ],
        ),
      ),
    );
  }
}

/// A complex class.
class ComplexData1 {
  /// Create a complex object.
  ComplexData1(this.data);

  /// The data.
  final String data;

  @override
  String toString() => 'ComplexData1(data: $data)';
}

/// A complex class.
class ComplexData2 {
  /// Create a complex object.
  ComplexData2(this.data);

  /// The data.
  final String data;

  @override
  String toString() => 'ComplexData2(data: $data)';
}

/// A codec that can serialize both [ComplexData1] and [ComplexData2].
class MyExtraCodec extends Codec<Object?, Object?> {
  /// Create a codec.
  const MyExtraCodec();
  @override
  Converter<Object?, Object?> get decoder => const _MyExtraDecoder();

  @override
  Converter<Object?, Object?> get encoder => const _MyExtraEncoder();
}

class _MyExtraDecoder extends Converter<Object?, Object?> {
  const _MyExtraDecoder();
  @override
  Object? convert(Object? input) {
    if (input == null) {
      return null;
    }
    final List<Object?> inputAsList = input as List<Object?>;
    if (inputAsList[0] == 'ComplexData1') {
      return ComplexData1(inputAsList[1]! as String);
    }
    if (inputAsList[0] == 'ComplexData2') {
      return ComplexData2(inputAsList[1]! as String);
    }
    throw FormatException('Unable to parse input: $input');
  }
}

class _MyExtraEncoder extends Converter<Object?, Object?> {
  const _MyExtraEncoder();
  @override
  Object? convert(Object? input) {
    if (input == null) {
      return null;
    }
    switch (input) {
      case ComplexData1 _:
        return <Object?>['ComplexData1', input.data];
      case ComplexData2 _:
        return <Object?>['ComplexData2', input.data];
      default:
        throw FormatException('Cannot encode type ${input.runtimeType}');
    }
  }
}